UI/UX Atlas
Design Systems Advanced

Token Naming Conventions & Tier Architecture

Poorly named tokens fracture design systems at scale — learn the three-tier primitive→semantic→component model and the naming conventions that keep tokens maintainable across platforms.

8 min read

The full lesson

Design tokens are only as powerful as the naming system behind them. A flat list of hex values named after their appearance — blue-500, gray-200 — gives you a shared vocabulary, but not shared intent. When a designer swaps the brand’s primary blue for indigo, every component that referenced blue-500 breaks semantically, even if its value is technically correct. The three-tier architecture — primitive, semantic, component — solves this. Pair it with the right naming conventions and your token library becomes a living design language that scales across platforms, themes, and time.

Why Naming Is Architecture

Token names are contracts between design and engineering. A name like color-background-interactive-hover tells you the role, context, and state without opening a single file. A name like #0057FF tells you nothing. That gap becomes critical at scale for four reasons:

  • A single product might have 300–800 tokens spanning color, typography, spacing, elevation, and motion.
  • Multiple platforms — web, iOS, Android, email — consume the same token source but need different output formats.
  • Theming (brand variants, dark mode, high-contrast, white-labeling) requires swapping values without touching component code.
  • Designers and engineers must reason about tokens independently, without constant back-and-forth.

The naming convention is what makes all of those use cases possible at once.

The Three-Tier Model

The industry has settled on a three-tier hierarchy. It aligns with how the W3C Design Token Community Group (DTCG) stable format structures references. Each tier has a specific job and a rule about what it can reference.

Tier 1: Primitive Tokens

Primitives — also called “global” or “reference” tokens — are the raw material. They list every possible value the system will ever use, before any meaning is attached.

{
  "color": {
    "blue": {
      "100": { "$value": "oklch(96% 0.04 260)", "$type": "color" },
      "500": { "$value": "oklch(55% 0.22 260)", "$type": "color" },
      "900": { "$value": "oklch(25% 0.18 260)", "$type": "color" }
    }
  },
  "space": {
    "1":  { "$value": "4px",  "$type": "dimension" },
    "2":  { "$value": "8px",  "$type": "dimension" },
    "4":  { "$value": "16px", "$type": "dimension" },
    "8":  { "$value": "32px", "$type": "dimension" }
  }
}

Four rules govern primitives:

  • They never reference other tokens — only hard-coded values.
  • They are never used directly in components. They exist only to be referenced by semantic tokens.
  • Their names reflect what the value is (hue, step, scale position), not what it means.
  • Use the W3C DTCG format with $value and $type keys. Avoid tool-proprietary formats that lock you into a single pipeline.

Modern practice uses OKLCH for color primitives because it produces perceptually uniform steps. The jump from blue-400 to blue-500 feels the same as blue-700 to blue-800. HSL-based scales produce uneven perceived contrast — which makes guaranteeing accessible color pairings algorithmically much harder.

Tier 2: Semantic Tokens

Semantic tokens are named for role and context. Their values are aliases — references to primitive tokens, not hard-coded values.

{
  "color": {
    "action": {
      "primary": {
        "default": { "$value": "{color.blue.500}", "$type": "color" },
        "hover":   { "$value": "{color.blue.600}", "$type": "color" },
        "pressed": { "$value": "{color.blue.700}", "$type": "color" }
      }
    },
    "surface": {
      "page":   { "$value": "{color.neutral.50}",  "$type": "color" },
      "raised": { "$value": "{color.neutral.100}", "$type": "color" },
      "overlay":{ "$value": "{color.neutral.200}", "$type": "color" }
    },
    "feedback": {
      "error":   { "$value": "{color.red.500}",    "$type": "color" },
      "success": { "$value": "{color.green.500}",  "$type": "color" },
      "warning": { "$value": "{color.amber.400}",  "$type": "color" }
    }
  }
}

This is where theming happens. A dark-mode theme file contains a separate semantic layer that maps the same semantic names to different primitive values:

{
  "color": {
    "surface": {
      "page":   { "$value": "{color.neutral.950}", "$type": "color" },
      "raised": { "$value": "{color.neutral.900}", "$type": "color" }
    }
  }
}

Swapping themes means swapping the semantic-token file. No component code changes. That is the architectural payoff.

Tier 3: Component Tokens

Component tokens are scoped to a single component. They reference semantic tokens (or, rarely, primitive tokens for truly unique values). They give component authors explicit override points without exposing the full semantic layer.

{
  "button": {
    "background": {
      "primary-default": { "$value": "{color.action.primary.default}", "$type": "color" },
      "primary-hover":   { "$value": "{color.action.primary.hover}",   "$type": "color" }
    },
    "radius": { "$value": "{border.radius.md}", "$type": "dimension" },
    "padding-x": { "$value": "{space.4}", "$type": "dimension" }
  }
}

Component tokens are optional, but they are valuable when a system needs per-component theming — for example, white-labeling button styles independently of other interactive elements — or when exposing a CSS custom-property API to consumers.

Naming Conventions in Practice

Consistent naming patterns enable search, code completion, and shared mental models. The most defensible structure follows this pattern:

{category}-{property}-{variant}-{state}
SegmentPurposeExamples
categoryBroad token typecolor, space, font, border, shadow, motion
propertyWhat aspect is being styledbackground, text, border, radius, size, weight
variantRole, component, or conceptaction, surface, feedback, interactive, brand
stateInteractive or conditional modifierdefault, hover, focus, disabled, selected

Not every token needs all four segments. A typography size token might only need category and scale step: font-size-md. What matters is consistent application — so any engineer can predict a token name without looking it up.

Case and Delimiter Standards

  • Use kebab-case in JSON source files (color-action-primary-hover).
  • Most build tools transform to the target platform’s convention automatically: CSS custom properties keep kebab-case, Swift uses camelCase, Android XML uses snake_case. Let the toolchain handle the transformation — never maintain separate per-platform files with hand-copied values.
  • Avoid camelCase or PascalCase in source files. They serialize poorly across platforms and are harder to scan in long token lists.

What Not to Name

The most common naming mistakes:

  • Value-descriptive names: color-bright-blue, color-dark-gray. These become wrong when the value changes, and they make theming impossible.
  • Component-specific names in the semantic tier: color-button-blue. Semantic tokens should express system-wide roles. If color-action-primary already exists, color-button-blue is a duplicate that will drift out of sync.
  • Abbreviations and acronyms: clr-bg-int-hov. Token names are read far more often than written — clarity beats brevity every time.
  • Role mixed into primitive names: blue-hover in the primitives layer smuggles meaning into raw values, which breaks the tier architecture entirely.

Do

Name semantic tokens by role, context, and state: color-surface-raised, color-text-secondary, color-feedback-error-default. Reference primitives by alias in the semantic layer. Let the build pipeline transform naming conventions per platform. Keep primitive names value-descriptive (scale position) and semantic names role-descriptive.

Don't

Name tokens after their current appearance: color-light-blue, color-off-white, color-dark-border. Use primitives directly in component code. Create parallel flat token files per platform with duplicated hard-coded values. Mix role into primitive names (blue-hover) or mix values into semantic names (color-blue-500-button).

Theming and the Semantic Layer

The semantic layer is what makes multi-theme design systems manageable. Without it, dark mode requires either CSS overrides on every component or a separate full token file per theme — and both approaches diverge immediately.

The correct model has three parts:

  1. One primitive token file — all possible values, platform-agnostic.
  2. One default semantic token file — maps all semantic names to their light-mode or default-theme primitives.
  3. One override semantic file per theme — dark mode, high-contrast, brand variant. Only the tokens that change need to appear in the override file. Everything else inherits from the default.

Dark-mode surfaces should use a near-black value like oklch(8% 0.01 260), not #000000. Pure black produces extreme contrast and makes luminance-based elevation invisible. Elevation in dark mode works by using subtle lightness steps — color-surface-page, color-surface-raised, color-surface-overlay should use progressively lighter neutrals. Drop shadows disappear against dark backgrounds, so elevation must come from color, not shadow.

The semantic-override approach also makes WCAG 2.2 AA compliance easier to maintain. You can run automated contrast checks against semantic pairs — color-text-primary on color-surface-page — and validate every theme in one pass, rather than hunting individual component states one by one.

Structuring Tokens in the W3C DTCG Format

The W3C DTCG stable JSON format (as of 2024) is the cross-tool standard for token source files. Here are the key properties:

KeyPurpose
$valueThe token’s value — either a literal or a reference {token.path}
$typeThe value type: color, dimension, fontFamily, fontWeight, duration, cubicBezier, number, strokeStyle, border, shadow, gradient, typography
$descriptionHuman-readable documentation string
$extensionsVendor-specific metadata (e.g., Figma variable IDs, Tokens Studio metadata)

Groups are nested JSON objects. The full dot-path of the object hierarchy forms the token name: color.action.primary.default.

Avoid proprietary formats for source tokens. Tools like Tokens Studio for Figma, Style Dictionary, and Theo all support the DTCG format or have migration paths to it. Proprietary formats create lock-in and require manual translation when the team switches tools — which happens on nearly every system that outlives a three-year product cycle.

Token Governance and Versioning

Naming conventions only hold if there is a process that enforces them. Here are four practical steps:

  • Lint token names in CI. A JSON Schema validator on the token source file catches naming convention violations before they merge. Tools like token-transformer and Style Dictionary support custom name validators.
  • Add $description to every semantic token. Undocumented tokens get misused. A description like “Use for all interactive control backgrounds in their default resting state — not for decorative surfaces” prevents the semantic layer from drifting into a flat list of colors that happen to have role names.
  • Version tier-2 token names as public API. Renaming a semantic token is a breaking change for every consumer. Treat semantic token renames like major version bumps: deprecate the old name, publish both in parallel for one release cycle, then remove the old one.
  • Don’t add component tokens prematurely. Every component token you add is a commitment to maintain. Audit quarterly and remove tokens that have zero consumer references.

Common Pitfalls at Scale

Systems that start well often degrade under shipping pressure. Watch for these patterns:

  • Semantic sprawl: Teams add hyper-specific tokens (color-sidebar-header-background-active) instead of reusing general ones. The semantic layer should stay small — 50–120 tokens for most products. If it grows past 200, audit for duplicates and over-specificity.
  • Tier skipping: Components reference primitive tokens directly to “ship faster.” This breaks theming and must be caught in code review.
  • Designer/engineer divergence: Figma variables and code tokens fall out of sync when the build step is manual. Automate the sync — Tokens Studio, Specify, or a custom GitHub Action that exports Figma variables to the token source file.
  • State explosion: Not every component state (hover, focus, pressed, disabled, selected, indeterminate, loading, error) needs its own semantic token. Map states to a shared action tier: color-action-primary-hover applies to any primary interactive element in a hover state, not just buttons.