You're 3 hours into a design review when the client says "can we try the button in teal instead of blue?" Your designer updates the Figma file. Your developer opens 14 component files. Twenty minutes later, 3 of them still say blue. This is what happens when your design decisions don't map to CSS custom properties as design tokens — you're managing color in a hundred places instead of one.
What makes a token system actually work
A mature token system has three levels, each serving a different purpose:
- Primitive tokens — the raw values.
--color-blue-500: #3b82f6 - Semantic tokens — purpose-driven aliases.
--color-primary: var(--color-blue-500) - Component tokens — scoped to specific components.
--btn-bg: var(--color-primary)
Dark mode in a CSS variables design system
This is where the semantic layer pays off in a CSS variables design system. Dark mode requires changing the semantic token values, not touching a single component. Every component that references --color-surface or --color-text automatically switches — no component-level changes needed.
Naming conventions that hold up as teams grow
- Use a
--category-variant-statepattern:--color-primary-hover - Never name by value:
--blueis meaningless the moment you change the blue - Name by role:
--color-actioncommunicates intent regardless of which hex value sits behind it
Auditing token resolution live
Use onHover's Element Inspector — built into the Chrome extension — to see computed CSS custom property values on any element. Hover the element, open the computed panel, scroll to the custom properties section — you'll see the resolved chain from component token all the way back to the primitive value.
Figma to code with the design token workflow
Tokens Studio (formerly Figma Tokens) lets you define tokens in Figma and export them as JSON. Style Dictionary then transforms that JSON into CSS custom properties, Tailwind config, or whatever format your stack needs. One definition in Figma becomes every output format your codebase requires — no manual translation between design and code.
The discipline that makes this work: every design decision in Figma must map to a token. If a designer picks a one-off hex value, that's a process failure — the fix is to add a token, not to hardcode a value in CSS.
Building the token system: where to start
The most common mistake we see is teams starting with component tokens before primitives are defined. You end up with component tokens that reference hardcoded hex values instead of primitives — which is only marginally better than hardcoding directly in components. You've added a layer of indirection without actually building the system.
The right order matters. Define all primitive color tokens first — the full palette, all weights, every neutral and brand color your product uses. Then create semantic tokens that reference only primitives. --color-primary references --color-blue-500, never a hex directly. Then define component tokens that reference only semantic tokens. --btn-primary-bg references --color-primary, never a primitive directly.
When this chain is intact, the teal-instead-of-blue request from the opening example becomes a one-line change. --color-primary gets pointed at --color-teal-500 instead of --color-blue-500. Every component token that traces back to --color-primary updates automatically. No file hunting, no three-still-say-blue problem.
Any place where a component or semantic token reaches past its layer to reference a raw value is a crack in the system. It'll cause maintenance pain as the codebase scales, and it will cause confusion when someone audits the token structure later and finds the chain broken. Keep each layer referencing only the layer below it — that constraint is the whole point.
Implementing tokens in React and Tailwind projects
CSS custom properties work in any React project that loads a global CSS file. No library required. The primitives and semantics live in :root, and components reference them through the cascade. That's it.
The challenge is Tailwind. Most Tailwind projects do all styling through utility classes, which means CSS custom properties sitting in :root aren't automatically available as utilities. The solution is Tailwind's theme extension in the config:
That configuration makes Tailwind generate bg-primary, text-primary, and border-primary classes that resolve to your token value. Change the CSS custom property and all Tailwind classes that reference it update automatically — no Tailwind config changes, no rebuild of the config, just a CSS cascade update.
This is actually how we handle theming in onHover's web properties. Tokens are defined in CSS, referenced in the Tailwind config, consumed as utility classes throughout the codebase. The token is the single source of truth. Tailwind is the consumption mechanism. When a color needs to change, it changes in one place — the CSS token — and propagates everywhere Tailwind is using it.
Token debugging: finding where a value actually comes from
CSS custom property chains can get deep. A button's background might resolve through --btn-primary-bg → --color-action → --color-brand-500 → the actual hex value. That's three hops. In a large codebase with many components, chains like this are normal. Tracing them manually is tedious.
The computed styles panel in DevTools shows resolved values but doesn't show the chain. You see the final hex, but you don't know which tokens got you there. If a button is rendering the wrong color, the computed styles panel tells you the wrong color it's rendering — not which token in the chain is responsible.
The onHover Element Inspector resolves this by showing the full token chain for any element you hover. Open the onHover Chrome extension, hover the button, open the computed panel, find the --btn-primary-bg property — the inspector shows its value, and for custom property references, shows the chain it resolved through. Each hop in the chain is visible.
This is particularly useful when a token is being overridden somewhere in the cascade and you need to find where. A component-level override, a scoped theme, a third-party stylesheet — any of these can intercept the resolution chain. The onHover inspector shows exactly which selector and which file introduced the value you're actually seeing rendered, so you can fix the override instead of guessing at the cascade order.
Token override hunting
If a component's color looks wrong and you're not sure why, the chain view in onHover's computed panel shows which selector is winning the cascade. Third-party component libraries that define their own custom properties are a common source of unexpected overrides — the chain view makes this visible in seconds.
Tokens across platforms: web, iOS, and Android
Design tokens aren't a CSS concept. They're a product concept that CSS happens to implement well through custom properties. The same primitive and semantic token structure that drives your web UI can drive native iOS components (via Swift package), Android (via Kotlin/XML resource files), React Native (via StyleSheet constants), and even marketing email templates (via inline CSS generation from the same JSON source).
Style Dictionary — the most mature token transformation tool in the ecosystem — handles all of these targets from a single JSON token definition. You define your tokens once. Style Dictionary transforms them into platform-native formats for every target your product ships to. iOS gets Swift constants. Android gets XML color resources. Web gets CSS custom properties. All from the same source file.
The implication for product teams is significant. A brand color change that used to require PRs in four separate repositories, coordinated across four different teams working in four different languages, can become a single token change that propagates everywhere through automated CI pipelines. One PR. One review. One merge. Every platform updated.
That's the end goal of a mature token system — not just "consistent buttons across your React components" but genuine cross-platform consistency with one source of truth. The web CSS token work is the foundation. Expanding it to other platforms is mostly a Style Dictionary configuration problem, not a new system problem.
Design tokens sit at the intersection of design and engineering, and the onHover for Designers page covers the inspection and handoff tools that make working across that boundary faster.