W3C Design Tokens 2025.10: Color Finally Has a Standard
DTCG's stable 2025.10 release defines OKLCH, Display P3, aliases, and JSON color tokens tools can finally agree on.
For a decade, design tokens meant whatever your Figma plugin exported on Tuesday. One tool emitted $color-blue-500. Another produced --blue-500. A third flattened modes into blue-500-light and blue-500-dark as unrelated strings. Translation pipelines multiplied. Aliases broke silently on rename. Wide-gamut greens became clip disasters because nobody agreed whether the JSON meant sRGB or Display P3. Engineering leads wrote bespoke validators that checked out on one repository and rotted on the next. Design-ops teams maintained spreadsheets because the JSON was not authoritative enough to trust in CI.
On 28 October 2025, the Design Tokens Community Group published the stable Design Tokens Technical Reports 2025.10 as a Final Community Group Report. The release bundles three modules: Format, Color, and Resolver. The specification is considered stable, with explicit intent that further updates arrive in superseding versions rather than perpetual draft churn. Color finally has a portable, tool-agnostic shape that anchors terminology to CSS Color Module Level 4 even when the runtime target is not a browser.
This guide explains what 2025.10 adds for color, how OKLCH and Display P3 fit the schema, how aliases and resolvers replace duplicated mode names, and what a stabilization project looks like when four years of ad-hoc JSON must converge before the next rebrand lands. The spec is a Community Group Report under the Final Specification Agreement, not a W3C Recommendation on the Standards Track. That distinction matters for procurement language but does not diminish the stability signal implementers waited for.
What stable 2025.10 changes for color tokens
The DTCG specification imports color science definitions from CSS Color 4 so token authors and browser vendors speak the same language about gamuts, white points, and conversion math. The color module requires "$type": "color" on color tokens and defines a structured "$value" object rather than ambiguous hex strings that tools guessed about. Tools must not infer type from value shape. If $type is missing and cannot be inherited from a parent group’s $type, the token is invalid. That rule ends the era of inferring probably hex means color.
A color value object carries required colorSpace and components fields, optional alpha defaulting to opaque, and optional hex as a six-digit sRGB fallback. The supported color spaces table in the Color Module is the interoperability contract. Web teams care most about srgb for legacy interchange, oklch for perceptual ramps and CSS authoring per oklch(), oklab for math and transform pipelines, display-p3 for wide-gamut brand greens, and hsl or lab/lch primarily for legacy imports rather than new authoring. The spec cites Björn Ottosson’s Oklab introduction for OKLAB and OKLCH background.
OKLCH chroma is unbounded in theory. Practice rarely exceeds roughly 0.5. Validators should warn on exotic values rather than crash, while designers taper chroma at ramp extremes where high chroma at high lightness looks fluorescent. Component slots may use the "none" keyword adopted from CSS Color 4 where zero is ambiguous, especially hue in achromatic colors. hsl(0, 0%, 100%) and hsl(none, 0%, 100%) both render white but interpolate differently during mixes. Tokens that feed color-mix() or gradients should use none where hue is absent per CSS Color 4 missing components.
{
"Hot pink": {
"$type": "color",
"$value": {
"colorSpace": "oklch",
"components": [0.7016, 0.3225, 328.363],
"alpha": 1,
"hex": "#ff00ff"
}
}
}
Wide-gamut brand colors belong in display-p3, not squeezed into sRGB hex alone. The optional hex fallback is intentionally a lowest-common-denominator sRGB preview, not a gamut-equal substitute. Translation tools should emit progressive CSS stacks with sRGB first and color(display-p3 …) enhancement where appropriate, paired with @media (color-gamut: p3) fallbacks per MDN color-gamut. Display P3 tokens require authors to know hardware context. The spec does not auto-detect user displays.
Files are JSON with recommended extensions .tokens or .tokens.json and preferred MIME type application/design-tokens+json. Validators in CI should reject non-JSON, enforce $type on color leaves, and verify colorSpace and components arity matches the spec table. Pin the spec version in repository documentation as DTCG 2025.10 until superseded so vendor contracts and internal runbooks reference a stable line.
The shift from hex strings to structured color objects is more than schema pedantry. Hex encodes sRGB and nothing else. When a wide-gamut green lives only as #22c55e, every downstream consumer must guess whether that hex is the authoritative value or a clipped preview of a richer intent. The 2025.10 shape forces authors to name the color space explicitly so translators can emit progressive CSS stacks, warn on gamut clipping during build, and preserve OKLCH components for color-mix() and relative color syntax without lossy round-trips through eight-digit shorthand. Teams that complained about verbose JSON before the migration often changed their minds the first time a single Display P3 edit propagated correctly to iOS, CSS, and documentation from one file.
Standardizing four years of ad-hoc JSON at a growing product company
The stabilization project began at a productivity software company whose token count had grown faster than its governance. Figma variables had been introduced in 2023 without a migration plan for the legacy JSON that Style Dictionary consumed. Two parallel sources of truth existed. Designers edited variables in Figma. Engineers hotfixed CSS custom properties in pull requests when campaigns needed colors faster than the plugin sync. Tokens Studio bridged the gap intermittently but exported different key casing depending on plugin settings nobody had documented.
The breaking incident was a rename. Brand design changed blue-600 to brand-primary in Figma collections. Tokens Studio produced a DTCG-shaped export with updated paths. Style Dictionary transforms still expected the old flat keys because a custom transform map referenced 2019 conventions. Production CSS kept emitting --blue-600 while Figma showed brand-primary. New components used semantic names from documentation. Legacy components still referenced primitives that no longer existed in the design file. Nobody noticed until a dark mode toggle shipped and half the settings page fell back to browser default blue because var(--blue-600) resolved empty.
The design-ops lead proposed freezing feature work on tokens until 2025.10 compliance landed in CI. Leadership agreed because the next rebrand was six months out and because enterprise prospects had started asking whether tokens were standards-based or tribal knowledge. The audit cataloged every color leaf in every JSON fragment, identified primitive versus semantic leaks, and tagged which values were hex-only versus already expressed with explicit color spaces in experimental branches.
Migration waves ran in order. First wave added $type: color on every color leaf and group-level inheritance where valid. Second wave converted hex-only values to colorSpace: oklch with hex fallback preserved for tools that still rasterized to six-digit sRGB. Third wave replaced duplicated mode names like text-primary-light and text-primary-dark with semantic aliases resolved through theme layers rather than parallel strings. Fourth wave expressed aliases as {dot.path} references instead of duplicate hex copies that drifted within weeks.
The human friction was naming politics, which the spec does not solve. Engineering preferred fg-default. Design preferred text-primary. The resolver module allowed both to exist as aliases to the same primitive during transition while lint rules blocked new primitive names in component code. The $root convention resolved a recurring ambiguity where color.accent needed to reference a base token inside a group that also contained hover and active children. Reference {color.accent.$root} for the base. Reference {color.accent.hover} for derivatives. The pattern mirrored semantic naming in CSS documented in Figma Variables to CSS Custom Properties.
Contrast verification remained outside spec compliance. Passing DTCG validation does not imply WCAG compliance. The team added a separate CI job that resolved semantic pairs to computed sRGB and ran the same contrast matrix they used before the migration. Spec compliance and accessibility evidence appeared in separate report artifacts so procurement reviewers could distinguish structure from user safety.
Training took one sprint. Developers learned that invalid tokens fail closed now rather than silently falling back. Designers learned that Figma variable names map to paths with consequences when renamed. The plugin export settings were pinned and checked into the repository README. By the time the rebrand arrived, the pipeline was Figma variables to Tokens Studio export as DTCG 2025.10 JSON to Style Dictionary custom transforms to CSS custom properties and Tailwind v4 @theme blocks. The handoff stopped being bespoke glue. The rebrand diff touched anchors and aliases, not four hundred ad-hoc string renames.
The project also surfaced how much organizational memory had lived inside one engineer’s laptop. Before 2025.10, the custom transform author was the only person who knew why blue-600 mapped to a different Figma variable in dark mode than in light mode. Documenting resolver layers in JSON made that knowledge legible to CI and to designers who would never read TypeScript. When that engineer went on parental leave mid-rebrand, the pipeline kept running because the spec-shaped file answered questions the team used to Slack about at nine p.m. on launch eve. That resilience is boring until you need it.
Aliases, resolvers, and translation without bespoke glue
The Format Module defines aliases as tokens whose $value references another token using curly-brace path syntax integrated with JSON Pointer semantics in the full specification. A brand accent can alias a blue primitive. Rename the primitive and semantic consumers update if the resolver chain stays intact.
{
"color": {
"blue": {
"600": {
"$type": "color",
"$value": {
"colorSpace": "oklch",
"components": [0.52, 0.17, 250],
"hex": "#2563eb"
}
}
},
"brand": {
"accent": {
"$type": "color",
"$value": "{color.blue.600}"
}
}
}
}
Groups organize tokens but must not be used to infer type. A group cannot be both a token and a container without explicit structure. The $root key provides a base token inside a group when children represent states or variants.
The Resolver Module addresses how token sets combine for themes such as light and dark, brand variants, and density modes. Instead of duplicating unrelated names per mode, resolvers map context to token set layers. A simplified mental model stacks global primitives at the bottom, semantic aliases in the middle, theme overrides above that, and platform extensions in $extensions for tool metadata like Figma collection identifiers. Exact resolver JSON syntax belongs in the module text pipeline architects should read before building a fourth home-grown theming hack.
CSS output from resolvers should target one semantic name with multiple resolved values rather than parallel names that diverge on the next rename.
:root {
--text-primary: oklch(0.22 0.02 260);
}
[data-theme="dark"] {
--text-primary: oklch(0.93 0.01 260);
}
Translation tools listed in the format module include Style Dictionary and Terrazzo. Custom transforms must map DTCG oklch components to oklch(L C H / alpha) CSS, display-p3 components to color(display-p3 r g b), hex fallbacks to the first line in progressive stacks, and alias references to var(--resolved-name) or flat custom properties depending on architecture. Style Dictionary v4 community transforms support DTCG formats but version compatibility must be verified before production migration.
Tailwind v4 consumes compiled custom properties in @theme blocks while DTCG JSON remains upstream source of truth. The framework emit step is a consumer, not an authority. Tokens Studio exports DTCG-shaped JSON from Figma variables and syncs with git repositories when plugin settings are pinned. The pipeline architecture matters more than any single tool brand.
Validator habits become culture faster than most teams expect. Once CI rejects a color leaf missing $type, pull requests stop sneaking in raw hex strings from campaign branches. Once alias references replace duplicate hex copies, designers see rename propagation in preview builds within minutes instead of discovering drift during QA. The spec does not automate good taste in chroma tapering or semantic naming, but it removes an entire category of silent failures that made taste irrelevant because nobody could trust the file anyway.
What 2025.10 does not solve and why it still matters
The specification does not resolve organizational naming politics, does not include contrast math in the color module, does not guarantee Figma and code stay synchronized without git discipline, and does not automatically detect wide-gamut hardware. It enforces structure so those human problems happen in the open rather than inside transform scripts nobody owns.
Procurement teams sometimes ask for W3C Recommendations specifically. The accurate answer is that 2025.10 is a stable Community Group Report with explicit supersession rules, published under FSA, and intended for implementation now. Waiting for a mythical Standards Track Recommendation before standardizing internal JSON has already cost teams years of incompatible exports. The stability declaration is the signal.
Store color in OKLCH when authoring for the web. Reference semantics, not primitives, in component code. Resolve themes without doubling names. Let Style Dictionary or Terrazzo compile to CSS while design tools sync through pinned export settings. Run contrast verification on resolved semantic pairs because spec compliance and accessibility evidence remain distinct artifacts.
Design Tokens 2025.10 is the line in the sand where OKLCH and Display P3 live in JSON with alpha rules, hex fallback semantics, none for missing hue, aliases via references, and resolver modules for theming. Color needed that line before the next plugin rename broke another alias chain at midnight on a launch weekend.
When your validators enforce $type and your translators read the same color space table, wide-gamut greens stop clipping silently and dark mode stops depending on duplicated string conventions held together by spreadsheet folklore. The work is migration discipline, not speculation about standards politics. The spec is stable enough to build on today.
If you are starting from scratch in 2026, begin with OKLCH primitives, semantic aliases, and resolver themes in DTCG-shaped JSON even before every tool in your stack advertises full 2025.10 support. Translators catch up faster when the source file is already valid. If you are migrating years of hex-only exports, resist the temptation to flatten everything in one panic commit. Wave the migration by module, pin plugin settings, and keep contrast verification separate from schema validation so accessibility evidence does not get lost in a sea of JSON reformatting. The specification gives you the shape. Your pipeline still needs owners who treat that shape as production infrastructure, not documentation theater.