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.
$type | Typical $value example | Notes |
|---|---|---|
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 |
fontWeight | 700 | Numeric 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 |
shadow | Object with offsetX, offsetY, blur, spread, color | Elevation shadows |
gradient | Array of color stops with position | Complex 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.
| Concern | Legacy approach (outdated) | W3C DTCG approach |
|---|---|---|
| Token structure | Tool-specific (Figma, Theo, Amazon) | Single $value/$type schema |
| Color authoring | HSL or hex hand-picked palettes | OKLCH perceptually-uniform values |
| References | Hard-coded duplicate values | {group.token} reference syntax |
| Semantic tier | Flat blue-500 aliases or none | Named purpose tokens pointing to primitives |
| Platform output | Separate hardcoded files per platform | Single source, build-tool transforms |
| Type safety | Optional, 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:
- Source — one or more DTCG JSON files, organized however your team prefers (by tier, by category, by brand). Style Dictionary merges them.
- Transform — per-platform value transformations: convert
oklch()tohexfor iOS (which doesn’t support OKLCH natively), convertpxtoremfor the web, convertmsto SwiftTimeIntervalfor animation tokens. - Format — the output format per platform: CSS custom properties, SCSS variables, a JavaScript ES module, a Swift
enum, a Kotlinobject, 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
$descriptionthat 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
#000000or#FFFFFFas dark/light surface backgrounds — luminance steps require room to move in both directions. - Skip the
$typekey — 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.