Crux Read real CSS, DTCG JSON, and a theme override; predict how the cascade resolves each token and pick the fix that keeps components theme-agnostic.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
Token bugs live in the CSS and the JSON, not the design doc. Read each snippet, trace how the cascade or the build resolves it, and choose the fix a senior would make first.
Goal
Practise reading token code the way you debug it: follow a reference through its tiers, predict what a theme override re-resolves, and spot the one line that locks a component out of theming.
A dark theme later redefines --color-interactive (not the primitive). After that, which element follows dark mode, and what is the fix for the other?
Heads-up Resolving to the same value today does not mean both track the override. The theme redefines the semantic name; .button reads the primitive, so it never sees the change.
Heads-up Custom properties cascade live: redefining --color-interactive re-resolves every var(--color-interactive) reference. .link recolors in one repaint; .button does not because it skipped the semantic tier.
Heads-up It is the opposite. Naming the primitive locks .button to that value; the theme overrides the semantic token, which only .link reads.
Style Dictionary builds this DTCG source for web and Android. What is true about how color.interactive and space.4 emerge per platform?
Heads-up The build resolves aliases before formatting; no platform receives the {ref} syntax. Android gets the resolved, transformed value, not the alias string.
Heads-up $type tells the transform how to convert the value, not to keep it fixed. CSS commonly transforms px to rem (1rem), and Android uses its own dimension format.
Heads-up JSON is the source, never the runtime artifact. Transforms adapt each value (px to rem, hex to ARGB) and formatters emit the actual CSS and XML files.
Toggling data-theme to dark recolors most of .card but leaves one visible defect. What is it and the fix?
Heads-up A light-gray border on a near-black surface is a visible two-tone defect. Borders are a themed surface decision and need a semantic token like the rest.
Heads-up [data-theme=dark] redefines the same custom-property names; the cascade re-resolves every var() that reads them. Only the hardcoded border, which is not a var(), fails to follow.
Heads-up That reintroduces JS for something the cascade does for free. Define a semantic border token under both selectors and the border themes itself in one repaint.
Snippet 4 — the fallback
.badge { /* --brand-accent is set by some host pages, missing on others */ color: var(--brand-accent); padding: var(--space-2, 8px);}
Quiz
Completed
On a host page that never defines --brand-accent, how does each property behave, and what does the var() fallback teach?
Heads-up One invalid custom-property reference does not nuke the rule. color becomes invalid-at-computed-value (uses inherited/initial), and padding resolves fine via its fallback.
Heads-up var() with no fallback yields the guaranteed-invalid value, triggering invalid-at-computed-value handling — inherited for color, not a hardcoded black. The reliable default is an explicit fallback.
Heads-up var(--name, fallback) is exactly the supported syntax; the second argument is used when the property is unset. That is why padding stays at 8px.
Recap
Token bugs are read in the CSS and the JSON: a component that names a primitive falls out of theming while its semantic-referencing sibling recolors; the DTCG build resolves aliases then transforms values so one source is correct per platform; a single hardcoded literal survives a theme toggle as a visible two-tone defect; and var() with a fallback is the seatbelt for tokens a consumer might not define. Trace the reference through its tiers, and the fix is almost always ‘reference the semantic token, with a fallback.’