Colour
The palette is written in two layers. Primitives hold the raw values and are the only place a hex appears; semantic tokens reference them and say what a colour is for, and components reference the semantic layer only. That is what makes a reskin a change to a dozen lines rather than to every component.
The whole colour layer is skin: the values here are the platform default and the five templates ship with them; a generator may replace the set. What does not change is the structure — the role each token plays, and the contrast each role has to clear.
The neutral ramp
Eleven near-neutral greys; click to copy a hex. The token export's ramp has fourteen steps — 600, 700 and 1000 belong to the platform's own editor chrome (grid lines, the shadow base), so a generator region never reaches for them. The three lightest (0 / 25 / 100) carry card, panel and control fill; the three darkest (800 / 900 / 950) carry secondary text, the brand colour and primary text.
A neutral workbench with one dark accent
Panels are bg-subtlest, cards on the panels are pure white, and the ground the preview stands on is bg-editor — three steps darker than the panels. The ground is the floor of the room and the panels are furniture standing on it; a preview reads as an object placed on that floor. Controls take a third grey, bg-control, one step lighter than the ground — a recess in a white surface rather than the floor itself.
Three surfaces a step or two apart are enough to separate floor, panel and card. Docked regions carry no shadow — only tone and a 1px stroke-default line.
brand-default is a near-black (neutral-900). It carries the primary button, the filled part of a slider, a switch in its on state, the selected tool in the rail and the selection ring on thumbnails. Hover lightens it one step to neutral-800 and press darkens it to neutral-950 — a press always lands darker than rest.
emphasize-default (atomm-600) is the platform's red and the one exception to a neutral chrome: at most one emphasized action per screen, on the thing that carries a cost or a consequence. The platform's own generators leave Generate on brand-default, so no template ships an emphasized button.
Four text steps
The first two clear 4.5:1 on every surface in the system. text-tertiary does not — 3.60:1 on white, 3.13:1 on the control fill. It follows that tertiary carries only text a reader can afford to miss: a helper line, a placeholder, an inactive label. Never a value, never a message, never a label a decision rests on. Disabled is not meant to be read at all.
Three strokes
Inputs have no border at rest: the grey fill is the affordance. A border appears only on focus, and then it is stroke-active.
Three neutral fills
They look close and they are not interchangeable:
6% is the hover step on a panel, 8% is a pressed or selected state there, and 12% is the row highlight inside a floating list. The first two land at 1.10–1.14:1 on the white ground of a popup and simply disappear, which is why a floating row has a step of its own — a menu row you cannot see is a menu row you cannot use.
Status
The chrome is neutral: brand-default carries everything that needs weight, and emphasize-default is rationed to one action per screen.
Parameters were recomputed against the latest model constraints.
File exported, 200 × 120 mm.
A 0.1 mm line really is a hair on screen. That is correct.
Width cannot exceed 300 mm.
Status colours work in pairs: the tint is the fill, the solid is the icon, and the words stay text-primary (15–17:1 on every tint). Coloured text on a coloured tint is the most common accessibility mistake in status messaging — and it would fail here, since green-default and warning-default measure 2.6–2.7:1 on their own tints. stroke-surround is a solid blue at 4.76:1 — often the only sign of where the keyboard is, so it clears 3:1 on its own with room to spare.
Reskinning
If you replace the palette:
- Stay light — the ground darker than the panels, never dark mode.
- Recompute every text step against every surface it sits on, and keep the two that carry meaning (
text-primary,text-secondary) at 4.5:1 or better. - Keep
stroke-divider/stroke-default/stroke-strongdistinguishable from each other and from the fills. - Change values, not names. Variables like
--color-brand-defaultare published to every generator; renaming one is a breaking change.