UI/UX Atlas
Design Systems Advanced

Design Tokens & the W3C DTCG Format

Master the W3C Design Token Community Group format — the shared, platform-agnostic contract that keeps design and engineering aligned at scale.

8 min read

The full lesson

Design tokens are named, platform-agnostic constants that capture every repeatable decision in a design system — color, spacing, typography, radius, duration, and more. The concept isn’t new, but the ecosystem now has one stable specification: the W3C Design Token Community Group (DTCG) format, ratified as a Candidate Recommendation in late 2024.

If your team still exports tokens as platform-specific JSON from Figma, maintains separate token files per codebase, or names things like blue-500 at the semantic layer, this lesson is for you.

Why Tokens Need a Shared Format

Before the W3C DTCG spec, every tool invented its own format. Figma Tokens, Style Dictionary, Theo, Amazon’s internal tooling, and iOS/Android style files all used different schemas, key names, type conventions, and ways of expressing references. The result: teams maintained multiple hand-crafted transformation scripts, and every tooling upgrade risked breaking the entire pipeline.

The DTCG spec solves the interoperability problem. It defines a single, tool-agnostic JSON structure that design and engineering tools can treat as a shared source of truth. When Figma, Tokens Studio, Style Dictionary, and your CI pipeline all read the same format, the transformation layer shrinks from custom code to simple configuration.

The spec is intentionally narrow: it defines how tokens are described, not how they are consumed. Consumption — CSS custom properties, Swift enums, Kotlin objects, Tailwind config — is handled by platform-specific build tools.

Anatomy of a W3C DTCG Token File

A DTCG token file is a JSON object. Tokens live in a nested hierarchy of groups. Every token is a JSON object with at least a $value key. The $type key declares what kind of value it is. The $description key is optional but strongly recommended.

{
  "color": {
    "$type": "color",
    "brand-primary": {
      "$value": "oklch(55% 0.22 264)",
      "$description": "Primary brand accent — interactive elements and links."
    },
    "neutral-100": {
      "$value": "oklch(97% 0.005 264)"
    }
  },
  "spacing": {
    "$type": "dimension",
    "scale-4": { "$value": "4px" },
    "scale-8": { "$value": "8px" },
    "scale-16": { "$value": "16px" }
  }
}

The $ prefix on reserved keys is a deliberate DTCG convention. It tells tools apart from group names. Any key without a $ prefix is a token or a group. Any key with $ is metadata about the containing token or group. Tools can use this to unambiguously distinguish structure from values.

Core Token Types

The spec defines a standard vocabulary of token types. Using the correct type tells consuming tools how to transform the raw value.

$typeTypical $value exampleNotes
color"oklch(55% 0.22 264)"Any CSS color; OKLCH recommended for perceptual uniformity
dimension"16px" or "1rem"Spacing, radius, font sizes, border widths
fontFamily["Inter", "sans-serif"]Array of font names
fontWeight700Numeric per CSS spec; avoid named weights
duration"200ms"Animation and transition timing
cubicBezier[0.4, 0, 0.2, 1]Easing curves as four-value arrays
shadowObject with offsetX, offsetY, blur, spread, colorElevation shadows
gradientArray of color stops with positionComplex gradients

Token References

DTCG supports cross-references between tokens using curly-brace syntax inside $value. References let you build a tiered architecture where semantic tokens point to primitives — without duplicating the same raw value in multiple places.

{
  "color": {
    "$type": "color",
    "brand-600": { "$value": "oklch(45% 0.22 264)" }
  },
  "interactive": {
    "action-default": {
      "$type": "color",
      "$value": "{color.brand-600}",
      "$description": "Default state for clickable actions."
    }
  }
}

At build time, consuming tools resolve {color.brand-600} to the actual primitive value for each target platform. The semantic token carries intent. The primitive token carries the literal value. This separation is fundamental.

The Three-Tier Architecture

Modern design token practice organizes tokens into three tiers. This is not a DTCG requirement — it is an architectural pattern that DTCG’s reference syntax makes possible. Flat, single-tier token files are an outdated habit that breaks down the moment you introduce theming or a second brand.

Tier 1 — Primitive tokens encode your full raw palette: every color step, every spacing value, every radius. They carry no semantic meaning. They exist so semantic tokens have something to point to.

Tier 2 — Semantic tokens encode intent: “the background color for a danger callout,” “the spacing between list items,” “the border radius for a card.” They reference primitives by name. This is the tier that changes between themes — a dark-theme semantic token points to a different primitive than the light-theme version.

Tier 3 — Component tokens encode per-component overrides for teams that need them. A button might have button.padding.x that points to a semantic spacing token but allows a component-level override without touching the global system. This tier is optional; many teams stop at semantic.

DTCG vs. Legacy Formats

Understanding what the spec replaced makes it easier to see what problems it solves.

ConcernLegacy approach (outdated)W3C DTCG approach
Token structureTool-specific (Figma, Theo, Amazon)Single $value/$type schema
Color authoringHSL or hex hand-picked palettesOKLCH perceptually-uniform values
ReferencesHard-coded duplicate values{group.token} reference syntax
Semantic tierFlat blue-500 aliases or noneNamed purpose tokens pointing to primitives
Platform outputSeparate hardcoded files per platformSingle source, build-tool transforms
Type safetyOptional, inconsistent$type on every token or inherited from group

The shift to OKLCH deserves special attention. Legacy Sass-based systems used darken() and lighten() on HSL hex values to generate tonal scales. The problem is that HSL does not model human lightness perception accurately, so the results look perceptually uneven. OKLCH does model it accurately. When you step the lightness channel at equal intervals in OKLCH, each step looks equally different to the human eye. This matters for accessible contrast ratios, dark mode adaptation, and algorithmic palette generation.

Building a Token Pipeline

A token pipeline takes your DTCG JSON source and produces platform-specific output. The dominant open-source tool is Style Dictionary (v4, which added native DTCG support in 2024). The pipeline has three stages:

  1. Source — one or more DTCG JSON files, organized however your team prefers (by tier, by category, by brand). Style Dictionary merges them.
  2. Transform — per-platform value transformations: convert oklch() to hex for iOS (which doesn’t support OKLCH natively), convert px to rem for the web, convert ms to Swift TimeInterval for animation tokens.
  3. Format — the output format per platform: CSS custom properties, SCSS variables, a JavaScript ES module, a Swift enum, a Kotlin object, or a Tailwind config extension.
{
  "source": ["tokens/primitives.json", "tokens/semantic.json"],
  "platforms": {
    "css": {
      "transformGroup": "css",
      "prefix": "ds",
      "buildPath": "dist/",
      "files": [{ "destination": "tokens.css", "format": "css/variables" }]
    },
    "ios": {
      "transformGroup": "ios-swift",
      "buildPath": "dist/ios/",
      "files": [{ "destination": "Tokens.swift", "format": "ios-swift/class.swift" }]
    }
  }
}

Running style-dictionary build traverses the token graph, resolves all references, applies transforms, and writes the output files. The DTCG source becomes the single source of truth. No platform team writes tokens by hand.

Theming with DTCG

Theming — switching between light/dark mode, multiple brands, or high-contrast variants — is where the three-tier architecture pays off. A theme is simply a second set of semantic token values that override the defaults.

In CSS, this maps directly onto custom property overrides scoped to a data attribute or class:

/* Base (light) theme — generated from light semantic tokens */
:root {
  --ds-surface-default: oklch(98% 0.003 264);
  --ds-text-primary: oklch(12% 0.01 264);
  --ds-action-default: oklch(55% 0.22 264);
}

/* Dark theme override — generated from dark semantic tokens */
[data-theme="dark"] {
  --ds-surface-default: oklch(14% 0.01 264);
  --ds-text-primary: oklch(94% 0.005 264);
  --ds-action-default: oklch(68% 0.2 264);
}

Notice that the dark theme does not simply invert values. It uses a near-black surface (oklch(14%...)) rather than pure black. Pure #000000 as a background is jarring against any colored foreground, and it eliminates the luminance-based cues that signal elevation. Each semantic token points to a different primitive chosen for the dark context. The elevation hierarchy is expressed through lightness steps, not box shadows — because box shadows disappear on dark surfaces.

Do

  • Name semantic tokens by purpose: action.default, surface.raised, feedback.danger.background.
  • Use OKLCH for color values in your primitive tier for perceptually uniform tonal scales.
  • Reference primitives from semantic tokens using the DTCG {group.token} syntax — never duplicate raw values.
  • Build a CI pipeline that runs Style Dictionary on every token commit, publishing platform artifacts automatically.
  • Give every token a $description that states where and why it is used, not just what it looks like.

Don't

  • Name semantic tokens after their color: primary: blue-500 — this couples intent to a specific hue and breaks when themes change.
  • Maintain separate, hand-edited token files for each platform — they will drift the moment anyone edits one without updating the others.
  • Use pure #000000 or #FFFFFF as dark/light surface backgrounds — luminance steps require room to move in both directions.
  • Skip the $type key — without it, build tools must guess the value type, which breaks cross-platform transforms.
  • Author tokens only in Figma without a git-backed DTCG source — the design tool becomes the source of truth and engineering has no canonical file to reference.

Tokens in the Design-to-Code Handoff

A well-structured DTCG pipeline makes handoff continuous and automatic instead of a periodic manual event. Developers reference token names — not hard-coded values — in components. When a token value changes in the source, a CI job regenerates platform artifacts and the change propagates everywhere that token is used.

This makes the old model of redline PDFs, Zeplin spec sheets, and static style guides obsolete. Those artifacts captured a snapshot of values at one moment in time. They drifted from the living codebase the instant any token was updated. With Storybook integrated alongside a token pipeline, developers see token names annotated on components in the context of living code — not a separate document.

Figma’s Dev Mode with Code Connect (released 2024) extends this further. Component instances in Dev Mode show the actual component code including token references, not generic style values. This requires your engineering tokens to be expressed as CSS custom properties that Figma can map back to named tokens.

Governance: Who Owns the Token Source?

With tokens as a shared contract, governance questions become real:

  • Who can add a new semantic token versus a new primitive?
  • What review process gates token changes that affect multiple platforms?
  • How are deprecated tokens communicated and removed?

A practical governance model treats the DTCG source files as code. Changes go through pull requests, reviewed by both a design system designer and an engineer. The files follow the same versioning discipline as a public API. Token additions are non-breaking. Token renames and deletions are breaking changes — they require a deprecation cycle with a $description note and a semver major bump.

Teams with more than one product often appoint a “token steward” — someone responsible for keeping the semantic tier consistent and preventing token proliferation. Token proliferation is the symptom where every one-off design decision spawns a new component-tier token instead of reusing existing semantics.