This skill is the operational reference for designing a token system that follows the W3C Design Tokens Community Group Format Module 2025.10 draft and emits CSS custom properties under cascade layers. It covers the DTCG JSON shape, the seven base types and six composite types, the alias resolution rule, the three-tier brand-to-component chain, the @property registration for animatable tokens, and runtime theme switching. The skill does NOT cover OKLCH palette generation (see [[frontend-theming-color-palette-oklch]]), dark / light mode implementation details (see [[frontend-theming-dark-light-mode]]), or specific tooling deep-dives.
CRITICAL : DTCG draft status (2025.10)
The DTCG Format Module is a W3C Community Group draft preview dated 7 May 2026 and is explicitly NOT production-implementation-ready per the spec preamble. Source : designtokens.org : Format Module draft (verified 2026-05-19).
Implications for production work today :
- ALWAYS author tokens in the DTCG JSON shape so future migrations are mechanical, not destructive.
- ALWAYS transform DTCG JSON to CSS custom properties via a build pipeline (Style Dictionary, Tokens Studio export, or a thin custom transformer). Do NOT consume the raw JSON at runtime.
- ALWAYS pin to the 2025.10 draft revision in your tooling. NEVER expect the format to be stable across draft versions until the spec reaches Candidate Recommendation.
- Disclose the draft status in any internal documentation that mentions DTCG conformance.
Quick Reference
Floor rules
- ALWAYS tier every token : primitive (raw brand atoms) -> semantic (intent like "action", "feedback success") -> component (button-specific override). NEVER skip the semantic tier and map primitive directly to component.
- ALWAYS emit tokens as CSS custom properties under
@layer tokens(or a similar dedicated layer). NEVER declare tokens in the default layer where any component rule can override them. - ALWAYS register a custom property via
@propertywhen it is transitioned, animated, or interpolated. NEVERtransitiona plain--var; the property is<custom-ident>by default and only swaps discretely. - ALWAYS namespace token names with category and intent :
--color-fg-action,--space-inline-md,--radius-control-sm. NEVER ship raw names like--my-bg,--blue,--big-radius. - ALWAYS use alias references inside DTCG JSON via
{group.token}. NEVER copy-paste a primitive$valueinto the semantic tier ; the link breaks at the first brand update. - ALWAYS transform DTCG JSON to CSS at build time. NEVER consume the JSON at runtime from a non-DTCG-aware loader.
- ALWAYS scope runtime theme overrides to a single cascade layer. NEVER toggle classes on
bodythat override component CSS by specificity ; layers keep override math predictable.
Decision tree 1 : Primitive vs semantic vs component ?
Is the value a raw brand atom : an exact hex from the brand book, a brand
typeface, a specific brand radius ?
-> PRIMITIVE tier. Name by appearance : --color-blue-500, --font-brand-sans,
--radius-12. These rarely change ; they document what the brand actually is.
Is the value an intent : "the color that means action", "the surface that
holds the page", "the typeface for body copy" ?
-> SEMANTIC tier. Name by role : --color-fg-action, --color-bg-surface,
--font-body. Reference a primitive : --color-fg-action: var(--color-blue-500).
Is the value a component-specific override : a button's bg, a tab's underline ?
-> COMPONENT tier. Name by component-and-role : --button-primary-bg,
--tab-active-underline. Reference a semantic : --button-primary-bg: var(--color-bg-action).
Are you tempted to skip the semantic tier and map primitive directly to component ?
-> Stop. The semantic tier exists so a brand-change PR touches one file
(primitive) and the semantic-to-component links stay intact.
Decision tree 2 : Static var or @property registration ?
Is the token transitioned, animated, or interpolated (gradient angle,
hue rotation, animated chroma) ?
-> @property registration is REQUIRED. Without it the property is treated
as <custom-ident> and only swaps discretely.
Is the token used only as a value passed to other rules (color lookup,
spacing scale, font family) ?
-> Regular custom property. No @property needed.
Is the token used in calc() or a CSS function that requires a known type ?
-> @property registration is RECOMMENDED. Type-safe substitution prevents
silent fallback to invalid-value when an upstream reference is wrong.
Does the token need a guaranteed initial value if a parent never sets it ?
-> @property with `initial-value`. Without registration the initial value
is the guaranteed-invalid value.
Decision tree 3 : Runtime theme switching strategy ?
Theme follows OS preference only (light / dark) ?
-> color-scheme: light dark on :root AND token values built with
light-dark(L, D). No JS required. Auto-respects the OS setting.
User can override the OS preference within the app ?
-> Toggle a data-theme attribute on :root or a region. Token layer
contains [data-theme="dark"] { ... } and [data-theme="light"] { ... }
overrides. Combine with light-dark() inside each override.
Multiple coexisting themes (per-tenant, per-section, per-customer brand) ?
-> Scope the data-theme attribute to a region container instead of :root.
Token overrides cascade from that region down. Combine with cascade
layers so component CSS cannot win specificity wars.
Need to prevent flash-of-wrong-theme on first paint ?
-> Set the data-theme attribute on the server-rendered HTML based on
a cookie / header. Render the chosen theme inline before the page
paints. Then hydrate the toggle in JS.
DTCG JSON shape (binding 2025.10)
Every leaf node :
{
"$value": "...",
"$type": "color" | "dimension" | "fontFamily" | "fontWeight" | "duration" | "cubicBezier" | "number" | "border" | "shadow" | "transition" | "strokeStyle" | "gradient" | "typography",
"$description": "Optional human prose.",
"$extensions": { "com.example.tool": { ... } }
}
Groups :
- Organize tokens hierarchically without a
$value. - May declare
$typeto set the inherited type for descendant tokens. - Reserved name
$rootrepresents a group's own value when needed.
Aliases :
- Form :
{group.subgroup.token}; resolves to the target's$value. - For nested-property access, JSON Pointer :
{ "$ref": "#/group/token/$value/property" }.
Source : designtokens.org : Format Module draft (verified 2026-05-19).
Base token types
| Type | Example $value |
Notes |
|---|---|---|
color |
{ "colorSpace": "oklch", "components": [0.60, 0.18, 250] } |
Modern form uses colorSpace + components. Hex string also accepted for sRGB. |
dimension |
"16px" or "1rem" |
Numeric + unit. |
fontFamily |
"Inter" or ["Inter", "system-ui", "sans-serif"] |
Single string OR array of fallbacks. |
fontWeight |
400 or "bold" |
1 to 1000 integer OR named (normal, bold, lighter, bolder). |
duration |
"200ms" |
ms or s. |
cubicBezier |
[0.2, 0, 0, 1] |
Four-element array. |
number |
1.5 |
Unitless. |
Composite token types
| Type | Shape |
|---|---|
border |
{ "color": <color>, "width": <dimension>, "style": <strokeStyle> } |
shadow |
{ "offsetX": <dimension>, "offsetY": <dimension>, "blur": <dimension>, "spread": <dimension>, "color": <color>, "inset": <boolean> } |
transition |
{ "duration": <duration>, "delay": <duration>, "timingFunction": <cubicBezier> } |
strokeStyle |
string : "solid", "dashed", "dotted", etc. |
gradient |
array of { "color": <color>, "position": <number 0..1> } |
typography |
{ "fontFamily": <fontFamily>, "fontSize": <dimension>, "fontWeight": <fontWeight>, "letterSpacing": <dimension>, "lineHeight": <number> } |
CSS emission rules
@layer tokens, theme, base, components, utilities;
@layer tokens {
:root {
/* Primitive tier : raw brand atoms */
--color-blue-500: oklch(0.60 0.18 250);
--color-blue-700: oklch(0.40 0.18 250);
--space-4: 16px;
--font-sans: "Inter", system-ui, sans-serif;
/* Semantic tier : intent */
--color-fg-action: var(--color-blue-500);
--color-fg-action-hover: var(--color-blue-700);
--space-inline-md: var(--space-4);
/* Component tier : component-specific override */
--button-primary-bg: var(--color-fg-action);
--button-primary-bg-hover: var(--color-fg-action-hover);
}
}
@layer theme {
[data-theme="dark"] {
--color-fg-action: var(--color-blue-300);
}
}
@layer components {
.button-primary {
background: var(--button-primary-bg);
}
.button-primary:hover {
background: var(--button-primary-bg-hover);
}
}
The cascade-layer order ensures token declarations always lose to theme overrides, theme overrides always lose to component rules that consume them. See [[frontend-syntax-css-cascade-layers-scope]] for the layering surface.
@property for animatable tokens
@property --gradient-angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.banner {
background: conic-gradient(from var(--gradient-angle), var(--color-blue-500), var(--color-blue-700));
transition: --gradient-angle 600ms;
}
.banner:hover { --gradient-angle: 360deg; }
Without @property, the transition does nothing because --gradient-angle is treated as a <custom-ident> and only swaps discretely.
Common syntaxes : <color>, <length>, <percentage>, <length-percentage>, <number>, <angle>, <time>, <integer>. Source : MDN : @property (verified 2026-05-19).
Token namespace conventions
Pattern : --<category>-<role>-<variant> (kebab-case).
| Category | Examples |
|---|---|
color |
--color-fg-action, --color-bg-surface, --color-border-subtle |
space |
--space-inline-md, --space-block-lg |
radius |
--radius-control-sm, --radius-surface-lg |
font |
--font-family-sans, --font-size-body, --font-weight-strong |
motion |
--motion-duration-quick, --motion-easing-emphasized |
shadow |
--shadow-elevation-1, --shadow-elevation-2 |
border |
--border-width-thin, --border-style-default |
Primitive layer keeps the raw appearance name : --color-blue-500, --space-4. Semantic and component layers use the role-based names above.
Cross-references
[[frontend-theming-color-palette-oklch]]: OKLCH palette generation, perceptually-uniform shade ladders that supply the primitive tier.[[frontend-theming-dark-light-mode]]:light-dark(),color-scheme, full dark-mode implementation patterns.[[frontend-syntax-css-cascade-layers-scope]]:@layerdeclaration order, scope rules, override math.[[frontend-syntax-css-color-modern]]:oklch(),color-mix(),light-dark(),color(display-p3 ...).[[frontend-impl-typography-system]]: type-scale tokens, font-loading strategy.
Reference Links
- references/methods.md : full DTCG JSON shape, all base + composite types with examples, alias-resolution rules,
@propertysyntax surface. - references/examples.md : renderable HTML demo with DTCG JSON snippet, layered CSS emission, theme switch, and
@property-animated gradient. - references/anti-patterns.md : eight anti-patterns with symptom, root cause, and fix.
Authoritative sources
- designtokens.org : Format Module draft (verified 2026-05-19, draft 2025.10 dated 7 May 2026, NOT production-ready)
- W3C : Design Tokens Community Group (verified 2026-05-19)
- MDN : Using CSS custom properties (verified 2026-05-19)
- MDN : --* (custom properties) (verified 2026-05-19)
- MDN : var() (verified 2026-05-19)
- MDN : @property (verified 2026-05-19)