Pulliv0.1
8px
04 — The Line

We took the dots and left the line.

This section exists because the system failed its own test. The lattice page describes the dots, and for a long time there was no page describing the line — which is the half that makes a kolam a kolam. This is the correction, and the record of why it was needed.

What went wrong

The origins page proposes a test for whether a borrowed metaphor is doing work: remove the source and see what collapses. Run honestly against the token layer, it came back almost empty.

LayerWhat the system hadWhat a conventional system hasVerdict
Spacing scale8 16 24 32 48 648 16 24 32 48 64identical
Radius scale2 4 8 162 4 8 16identical
Stroke weights1 2 41 2 4identical
Type ratio√2 modular√2 — ISO paperidentical
Durations120 240 360 600120 240 300 600near-identical
Breakpoints320 480 768 1024 1280320 480 768 1024 1280identical
Components12, rounded rectangles12, rounded rectanglesidentical
Not one token value would have changed. Only the names and the comments. The tradition was supplying vocabulary and rationale, not constraint — which is precisely the failure mode the origins page warns adopters about, committed by the system itself.

The diagnosis. The system derived from the kolam's grid, which is its least distinctive property. Every design system has a grid, so mapping onto that produced agreement with convention rather than divergence from it. It even produced a false sense of rigour: r = P/2 is a genuine geometric constraint inside the generator, and applied to a token scale it is arithmetically trivial — any system with an 8px base and a 4px radius base has that relationship by accident.

What was missing. The line. That it is continuous, that it traverses in an order, that it crosses itself, and that it encloses space by looping rather than by drawing a box. All of that went into an SVG file and none of it reached the interface.

What made the fix findable. The toggle. Rebuilt as two pulli on a kambi with a travelling loop, it is unmistakably kolam and still an entirely ordinary switch. If the line can come into one component without costing anything, there was no reason it was the only one.

The two moves this section adds

  • Wrap or pass — the two things a line can do at a dot, used as the system's selection primitive. Replaces borrowed conventions (fills, underlines, shadows) with a derived one.
  • Enclosure by looping — component boundaries as actual kolam boundaries, and a default corner treatment that turns the radius from a decorative softening into a visible loop around a visible dot.

Neither adds a token. Both change what the system looks and behaves like at every surface.

Wrap or pass

At any dot, the line does exactly one of two things. That is a binary, and selection is a binary.

passunselected · the dot is on the line but not wrapped
wrapselected · the loop closes around the dot
crosspartial · the third tile, for indeterminate state

These are not three states invented to fit an interface. They are the complete set of things the generator's three tiles do at a dot, which is the whole vocabulary a kolam has. Mapping selection onto them means the interface and the pattern engine are speaking the same language rather than sharing a mood.

It is shape, not colour. A loop is present or it is not. That survives greyscale, colour-blindness, and a monochrome printout without a second indicator — which is more than can be said for an accent fill.

The rule

Wherever a set of options sits along a line, the options are pulli on a kambi, and the chosen one is wrapped by a loop.

Tabs, segmented controls, steppers, nav rails, list selection — and the toggle, which is simply the two-option case. One rule, six components, no new tokens.

The dot rail

The same construction at four different jobs. Every one of these was previously a different borrowed convention.

Tabs — was an underline

The tab rail is the kambi. Each tab has a pulli on it. The current tab's dot is wrapped.

Segmented — was a sliding pill

Stepper — was numbered circles

  1. Lay the pulli
  2. Choose symmetry
  3. Draw the line
  4. Close the boundary

Toggle — the two-option case

dot 0.5P · loop 2.5P · kambi hairline · loop scale 0→1 over 240ms neli
What this replaced. An underline indicator, a sliding pill with a shadow, numbered circles with tick marks, and a filled thumb — four conventions from four different systems, none of which had any relationship to the others or to the pattern layer. They are now one construction with one specification.

Enclosure

A kolam does not enclose space with a box. It encloses by looping the dots along the perimeter.

1 · Corner pulli — the default

The cheapest correction in the system, and the one now applied to every panel and card on this site. The corner radius was already one loop radius — that fell out of r = P/2. The only thing missing was the dot at the centre of the arc.

Drawing it turns a rounded corner from a decorative softening into a visible loop around a visible dot. Four radial gradients, no JavaScript, correct at any element size and any pitch.

Look at the corners of this panel, and of every other panel on the site.

Applied by default to

  • .panelyes
  • .cardyes
  • .modal-demoyes
  • .kolam-cornersutility
  • .no-cornersopt out

2 · The curl boundary — opt-in

The edges stay straight, and the line curls at each corner — three-quarters of a turn around the corner dot before continuing down the next edge. It is the boundary gesture without the frill, and the curl radius is fixed at --radius-panel rather than proportional, so a small card and a wide banner get the same move at the same size.

A bounded panel

This panel's edge is generated at its measured size by Kolam.frame() and redrawn on resize. The dot inside each curl is the thing the line is turning around.

Drag the pitch control in the header — the curl radius follows it.

Accented

The same boundary in the accent role. Below 4 × radius in either dimension it degrades to a plain rounded rectangle rather than producing a broken curl.

<div class="panel kolam-bound accent">

What this replaced, and why. The first version wrapped every perimeter dot — the geometrically literal kolam boundary, outward half-loops the whole way round. It is still available as style: "scallop" and it is the wrong default: on a UI surface it reads as a doily, because the loops in a real kolam boundary are terminations of the interior pattern, and with an empty interior they are just frills. The literal transcription was worse than the gesture. That is worth stating in a system whose whole argument is fidelity to a source.
// the boundary is geometry, not an asset
const { path, dots } = Kolam.framePath(320, 180, { style: 'curl', radius: 16 });

// or let it size and redraw itself
<div class="card kolam-bound">…</div>

3 · The threshold band

The most faithful use of a kolam on a surface, and the last one we found. A kolam is not a frame around a room — it is a mark at the threshold, at the point of entry. On a card or a panel the threshold is the head, where the eye enters. So: one motif, repeated, along the top edge.

Margazhi 12

A real generated kolam at the head of the surface, not a border around it. Seed 41, repeated at an 8P cell.

A denser motif

Seed 7 with crossings. The band changes with the seed, so a product can carry a motif the way it carries a logo.

One motif, repeated — not a different seed per cell. Varying the seed along the band produces noise; repeating one produces a border. This is the single decision that makes it read as a threshold mark rather than as a row of unrelated figures, and it took rendering both to see it.

Where the line stops

A correction that is applied everywhere is a different kind of mistake. These are the places the line was deliberately kept out.

Three of these were wrong. The first version of this table excluded the radio, the checkbox and table rows on affordance and density grounds. All three objections were real — and all three had derivations that keep the affordance rather than trade it away. The corrected reasoning is below; the original verdicts are kept so the change is visible.
ComponentFirst verdictNowReasoning
Radiokept nativederived The objection — that a wrapped dot reads as a radio and would collide with the checkbox — was solved by giving the two controls different moves rather than the same one. Radio is the loop closing: dashed when open, solid when closed. Continuity carries the state, so it survives greyscale.
Checkboxkept nativederived The square enclosure stays, so the exclusive-versus-independent distinction is untouched. Only the mark inside changed, to tile C — the crossing. Indeterminate falls out for free as a single arm. The learned shape was never the thing that had to be borrowed; the tick was.
Tabletinted rowsderived The density objection confused two cases. A loop on every row is line work at frequency and stays forbidden — hover is still a tint. A loop on one selected row, and one on the sorted column's header dot, is a mark, not a texture.
Progresspill and fillderived Never argued against, just never revisited. Progress is how far the line has travelled: a kambi with pulli, the drawn portion at full weight, and a loop at the head.
Focus ring2px outlineunchanged Still excluded. The most safety-critical indicator in the system. It must be instantly recognisable to keyboard users across every product that adopts this, and novelty is most expensive exactly here.
Buttonsfilled / outlinedunchanged Still excluded. A button is not a choice among options along a line. Forcing the primitive here would be the metaphor overreaching — the same error as leaving it out, in the opposite direction.
The general rule still holds. The line goes where a set of options sits along a path, and where a surface encloses space. It does not go everywhere a designer could justify it. A metaphor applied without limit stops being a system and becomes a theme.
But "it would break the affordance" is a weaker argument than it sounds. In all three reversals the affordance was carried by something the derivation did not need to touch — the shape of the enclosure, the frequency of the mark. Check what specifically the user has learned before concluding the tradition cannot reach it.

The deletion test, again

Same test, re-run after the correction. Honest scoring: only things that would actually have to be redesigned count.

If kolam were removed…BeforeAfter
Token valuesunchangedunchanged
Pattern enginewould not existwould not exist
Togglewould be redesignedwould be redesigned
Tabsunchangedwould be redesigned
Segmented controlunchangedwould be redesigned
Stepperdid not existwould be redesigned
Panel & card cornersunchangedwould lose the pulli
Surface boundariesrounded rectangleswould lose the loop boundary
Radio & checkboxnativewould be redesigned
Table sort & selectionarrow + tintwould be redesigned
Progresspill and fillwould be redesigned
Selection languagefour borrowed conventionsone derived primitive, would be lost

Honest score

Before: two things collapsed — the generator and one component. After: the generator, nine components, the selection language across all of them, and every surface boundary in the system.

Still not everything. The token values remain conventional and probably should — an 8pt grid is not wrong just because it is common, and inventing an unconventional spacing scale to prove a point would be the metaphor overreaching again.

What would still be honest to say

That this is a conventional measurement layer with a genuinely kolam construction layer on top of it — the state language, the enclosure, and the pattern engine. Not "a design system derived entirely from kolam", which was never true and is still not.

The claim on the overview has been narrowed to match.

Why this page stays. It would be easy to fix the system quietly and delete the evidence that it was ever wrong. But the failure is the most instructive thing in the documentation: a metaphor can supply vocabulary and rationale for a long time before anyone notices it is supplying no constraint. The deletion test is cheap. Most systems have never run it.