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.
| Layer | What the system had | What a conventional system has | Verdict |
|---|---|---|---|
| Spacing scale | 8 16 24 32 48 64 | 8 16 24 32 48 64 | identical |
| Radius scale | 2 4 8 16 | 2 4 8 16 | identical |
| Stroke weights | 1 2 4 | 1 2 4 | identical |
| Type ratio | √2 modular | √2 — ISO paper | identical |
| Durations | 120 240 360 600 | 120 240 300 600 | near-identical |
| Breakpoints | 320 480 768 1024 1280 | 320 480 768 1024 1280 | identical |
| Components | 12, rounded rectangles | 12, rounded rectangles | identical |
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.
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
Segmented — was a sliding pill
Stepper — was numbered circles
- Lay the pulli
- Choose symmetry
- Draw the line
- Close the boundary
Toggle — the two-option case
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">
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.
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.
| Component | First verdict | Now | Reasoning |
|---|---|---|---|
| Radio | kept native | derived | 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. |
| Checkbox | kept native | derived | 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. |
| Table | tinted rows | derived | 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. |
| Progress | pill and fill | derived | 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 ring | 2px outline | unchanged | 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. |
| Buttons | filled / outlined | unchanged | 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 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… | Before | After |
|---|---|---|
| Token values | unchanged | unchanged |
| Pattern engine | would not exist | would not exist |
| Toggle | would be redesigned | would be redesigned |
| Tabs | unchanged | would be redesigned |
| Segmented control | unchanged | would be redesigned |
| Stepper | did not exist | would be redesigned |
| Panel & card corners | unchanged | would lose the pulli |
| Surface boundaries | rounded rectangles | would lose the loop boundary |
| Radio & checkbox | native | would be redesigned |
| Table sort & selection | arrow + tint | would be redesigned |
| Progress | pill and fill | would be redesigned |
| Selection language | four borrowed conventions | one 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.