A theme.json Architecture That Survives Six Client Handovers
Tokens, layers, and the naming convention we landed on after breaking it three times. Includes the starter file we now begin every project with.

theme.json is a design system contract disguised as a config file. Treated casually it becomes a thousand-line dumping ground. Treated as an architecture it removes most of the CSS you'd otherwise write.
Three layers, no exceptions
We split every project's tokens into primitives, semantics, and components. Primitives are raw values. Semantics map meaning onto them. Components are the only place a block name may appear.
Naming, so a stranger can read it
- Primitives: base-sand-100, base-ink-900, scale-4
- Semantics: surface, surface-raised, text, text-muted, accent
- Components: styles.blocks.core/button, styles.blocks.core/quote
Why editors break things
If your palette exposes fourteen colours, an editor will use fourteen colours. Expose five semantic slots with sensible names and the site stays coherent two years after you've stopped invoicing for it.
Constrain the palette, not the person. Nobody has ever thanked a theme for offering them 'Accent 7'.
Version it like code
We keep theme.json under review in the same PR as the markup that consumes it, and we diff generated CSS output in CI. A token rename that adds 6kb to the payload fails the build.