Part 1 · 2 chapters · ~20 min

Tokens

Tokens in three tiers (primitive values, semantic roles, component uses) with references flowing downward, naming that says the role, one source built to every platform, and theming as a semantic remap: dark mode as the theme that exposes every shortcut, the tokens it needs, what breaks, composing themes, and testing the matrix.

3

Three tiers of tokens

code
// tokens.json (W3C design tokens format): three tiers, references downward, one source for every platform
{
  "color": {
    "blue":  { "600": { "$type": "color", "$value": "#2563EB" }, "400": { "$type": "color", "$value": "#60A5FA" } },     // primitive
    "gray":  { "50": { "$value": "#FBFAF8" }, "900": { "$value": "#121110" }, "100": { "$value": "#F3EFE8" }, "800": { "$value": "#1E1C1A" } },
    "action": { "primary": { "$value": "{color.blue.600}", "$extensions": { "theme": { "dark": "{color.blue.400}" } } } },   // semantic, with a dark value
    "surface": { "default": { "$value": "{color.gray.50}", "$extensions": { "theme": { "dark": "{color.gray.900}" } } },
                 "raised":  { "$value": "#FFFFFF",          "$extensions": { "theme": { "dark": "{color.gray.800}" } } } },  // elevation as lightness in dark
    "text":    { "default": { "$value": "{color.gray.900}", "$extensions": { "theme": { "dark": "{color.gray.100}" } } },
                 "onAction": { "$value": "#FFFFFF",         "$extensions": { "theme": { "dark": "#0B1220" } } } }
  },
  "space": { "4": { "$type": "dimension", "$value": "16px" }, "inset": { "md": { "$value": "{space.4}" } } },
  "button": { "primary": { "background": { "$value": "{color.action.primary}" }, "text": { "$value": "{color.text.onAction}" } } }   // component → semantic
}
// build (Style Dictionary or the W3C tooling) → tokens.css:
//   :root { --color-action-primary: #2563EB; --color-surface-default: #FBFAF8; --button-primary-background: var(--color-action-primary); … }
//   [data-theme="dark"] { --color-action-primary: #60A5FA; --color-surface-default: #121110; --color-surface-raised: #1E1C1A; … }
// → tokens.ts: export const tokens = { color: { action: { primary: 'var(--color-action-primary)' } } } as const
// → Figma variables (light + dark modes) · iOS Color assets · Android colors.xml, all from this file
// a component: .btn-primary { background: var(--button-primary-background); color: var(--button-primary-text) }   // never a hex, never a primitive
a token is a decision with a name
  1. Primitive: the palette and scales named by what they are (color.blue.600, space.4, size.font.300, duration.150), generated from ratios and perceptual steps (OKLCH), never used directly by a component.
  2. Semantic: the role (color.action.primary, color.text.muted, color.text.onAction, color.surface.raised, space.inset.md, type.heading.lg), each referencing a primitive. The vocabulary designers and engineers share; what Figma variables and CSS custom properties expose. color.text.onAction is the token nobody adds until the first inaccessible button.
  3. Component: the use in one component (button.primary.background, input.border.error), each referencing a semantic; the override point for a brand or a variant without touching CSS.
  4. The direction: component → semantic → primitive, never the other way; a component naming a primitive cannot be themed; the lint (part 7) enforces it; the tooling resolves the chain at build.
  5. Naming: category.concept.property.variant.state; the name says the role, never the value; a value in a name is a primitive leaking and will be wrong the day the brand changes. Rename with a codemod (the Big-company FE course part 4).
  6. One source, every output: a JSON source in the W3C format built to CSS variables, TypeScript constants, Tailwind config, iOS and Android resources and the Figma variables file (the Architecture course part 4's distribution). One change, every platform.
THREE TIERS OF TOKENS
primitive values, semantic roles, component uses, and the direction references flow
swipe the figure sideways, or tap expand for full screen
1/6
primitive
Primitive: the full palette and scales, named by what they are, not what they do: color.blue.600, color.gray.100, space.4 (16px), size.font.300 (16px), radius.md, duration.150, easing.standard, elevation.2. Generated from scales (a type scale with a ratio; a spacing scale on a 4px base; a colour scale with perceptual steps in OKLCH). Never used directly by a component.
4

Theming, and dark mode as a token problem

a theme is a semantic tier remapped
  1. The mechanism: semantic tokens as custom properties on :root; a theme redefines them under [data-theme] (with prefers-color-scheme as the default and the attribute as the override); components use var(--color-surface-default) and never a hex. Flip the attribute, flip everything. These pages do exactly this, light as the base and dark opt-in.
  2. Dark mode is not inversion: near-black surfaces not black; elevation as lighter surfaces, not shadows (invisible on dark); brand colours lightened and desaturated for contrast; borders for edges; dark variants for images and illustrations. Each a semantic decision made once.
  3. The tokens dark mode needs that light mode let you skip: surface.raised and surface.overlay, border.subtle, text.onInverse, a dark value for action.primary, an elevation family that is shadow in light and lightness in dark. Add them before the dark day.
  4. What breaks, each a shortcut: a hex background; a primitive referenced directly; a box-shadow for elevation; a white-background PNG; an SVG fill not set to currentColor; a chart palette chosen for light; a third-party embed. Found by flipping and looking; prevented by the lint that forbids primitives and hex in component CSS (part 7).
  5. Multiple themes (brand × mode × density × contrast) compose as attributes on the root when each touches only its own tokens; the system defines which tokens each theme may redefine; a theme that redefines everything is a fork. A brand per country (the Platformization course part 3) is a theme.
  6. Testing themes: every story in every theme in the visual regression suite (part 7); a contrast check per theme over the enumerable text-over-surface pairs; a token change runs the whole matrix. A theme not in the suite is broken somewhere.
the exercise
Add [data-theme="dark"] to your product's root with only the surface and text tokens remapped, and screenshot every screen. Every element still light is a shortcut; the list is the token work the system owes.
THEMING, AND DARK MODE AS A TOKEN PROBLEM
a theme is a semantic tier remapped; dark mode is the theme that exposes every shortcut
swipe the figure sideways, or tap expand for full screen
1/6
the mechanism
The mechanism: the semantic tokens are CSS custom properties on :root; a theme redefines them under a selector ([data-theme="dark"], or prefers-color-scheme in a media query with the attribute as an override); components use var(--color-surface-default) and never a hex. Flipping the attribute flips every component at once; the Learna pages you are reading do exactly this with light as the base and dark opt-in.