Primitive and semantic token layers
The two-tier token architecture separates color vocabulary from color meaning. Primitive tokens (also called global or reference tokens) represent raw color values with no semantic intent: --brand-blue-500: #1E3A8A, --neutral-gray-200: #E5E7EB. These form the color vocabulary of the system. Semantic tokens reference primitives and attach usage context: --color-action-primary: var(--brand-blue-500), --color-surface-secondary: var(--neutral-gray-200). The rule that makes the architecture work: components and styles consume semantic tokens only, never primitives directly. When this discipline is maintained, updating a primitive value propagates coherently through the entire product via the semantic layer.
The dark mode test
Dark mode is the acid test for semantic token completeness. If components reference semantic tokens correctly, dark mode is a matter of remapping semantic tokens to different primitive values: --color-surface-secondary might reference --neutral-gray-200 in light mode and --neutral-gray-800 in dark mode. If components reference primitives directly, dark mode requires updating every component individually. The dark mode test: can you implement dark mode by changing only the semantic-to-primitive mappings, without touching any component styles? If the answer is no, the semantic layer has gaps. Common gaps: surfaces that reference gray primitives directly, text colors hard-coded to black or white, interactive states defined with specific hex values.
State and interaction tokens
Beyond surface and text tokens, a complete semantic layer covers interaction states: hover, pressed, focused, disabled, selected. These state tokens are often the most neglected part of token architecture — designers specify them inconsistently, engineers implement them ad-hoc, and the result is hover states that differ across components. A systematic approach: define base semantic tokens (--color-action-primary), then state variants (--color-action-primary-hover: var(--brand-blue-600), --color-action-primary-pressed: var(--brand-blue-700), --color-action-primary-disabled: var(--brand-blue-200)). Hover and pressed states are typically 1–2 lightness steps darker than the base; disabled states are significantly desaturated and lightened.
Token naming conventions
Naming conventions are a design decision with long-term maintenance consequences. Naming primitives by color and shade (brand-blue-500, neutral-gray-200) makes them legible as color values. Naming semantics by role and state (surface-primary, action-hover, feedback-error) makes intent clear without exposing implementation. Component-level tokens (the third tier) follow semantic naming patterns applied to specific components: --button-primary-background, --card-border-color, --input-placeholder-color. The non-negotiable discipline: maintain consistent naming across all tiers. Mixed naming conventions — some tokens named by color, others by role, others by component — create cognitive overhead and invite inconsistency. Pick a convention and enforce it.