Skip to content
Back to Design Hub

Design Tokens and Component Architecture

Token pipelines, naming conventions, and component composition strategies that scale across platforms.

Design TokensIntermediate

Why

Hardcoded colors and spacing create theme drift and make white-labeling impossible. Design tokens centralize decisions as named values, enabling dark mode, multi-brand theming, and cross-platform parity (web + iOS + Android) from a single source of truth.

Tokens Studio (formerly Figma Tokens) has 500k+ installs, and the W3C Design Tokens Format Module is nearing candidate recommendation. Tokens enable dark mode, multi-brand theming, and platform parity from a single source.

When

Introduce tokens when you have 2+ themes, ship to multiple platforms, or need design-engineering sync without Figma-to-code drift. Start with color + spacing; add typography and motion later.

How

1. Define token taxonomy — group by primitive (color), semantic (color.action.primary), component (button.bg.primary) 2. Choose format — Style Dictionary for multi-platform output (CSS vars, JS, Swift, Kotlin) 3. Author source — JSON or Tokens Studio in Figma, versioned in git alongside code 4. Build pipeline — style-dictionary build emits dist/tokens.css, tokens.ts, tokens.swift 5. Wire into components — components reference var(--color-action-primary), never raw hex 6. Add theme switcher — swap a data-theme attribute on <html> to load alternate token sets 7. Validate — stylelint-declaration-strict-value enforces no hardcoded values in CSS

Example

// tokens/color.json (Style Dictionary source) { "color": { "brand": { "primary": { "500": "#6366f1", "600": "#4f46e5" } }, "action": { "primary": { "value": "{color.brand.primary.500}", "type": "color" }, "hover": { "value": "{color.brand.primary.600}", "type": "color" } } } } // config.js — Style Dictionary const StyleDictionary = require("style-dictionary"); StyleDictionary.extend({ source: ["tokens/**/*.json"], platforms: { css: { transformGroup: "css", buildPath: "dist/", files: [{ destination: "tokens.css", format: "css/variables" }]}, ts: { transformGroup: "js", buildPath: "dist/", files: [{ destination: "tokens.ts", format: "javascript/es6" }]} } }).buildAllPlatforms(); /* dist/tokens.css (generated) :root { --color-action-primary: #6366f1; --color-action-hover: #4f46e5; } */ // Button.css — references tokens, never raw values .btn-primary { background: var(--color-action-primary); } .btn-primary:hover { background: var(--color-action-hover); }

Common Problems

  • ⚠Too many token tiers — primitive + semantic + component for every value creates 500+ tokens nobody uses
  • ⚠Tokens not exported to all platforms — web uses CSS vars but iOS gets hardcoded colors, drift returns
  • ⚠Renaming a token breaks every consumer — no codemod, so teams keep deprecated aliases forever
  • ⚠Theme override specificity wars — !important everywhere because component styles beat :root tokens

Best Practices

  • ✓Use 3 tiers max: primitive → semantic → component, and only add component tier when reuse is proven
  • ✓Generate platform outputs in CI — fail the build if tokens.css and tokens.swift diverge
  • ✓Write a codemod (jscodeshift) for token renames — run it in the same PR as the rename
  • ✓Keep theme overrides at :root[data-theme='dark'] — never override inside component CSS

FAQ

How many token tiers should I have?

Three max: primitive (color.blue.500) → semantic (color.action.primary) → component (button.bg). Add the component tier only when reuse is proven; otherwise you get 500+ unused tokens.

Style Dictionary vs Tokens Studio — which should I use?

They're complementary: Tokens Studio authors tokens in Figma and exports JSON; Style Dictionary compiles that JSON to CSS vars, JS, Swift, Kotlin. Use both in a pipeline.

How do I rename a token without breaking consumers?

Write a jscodeshift codemod that updates all references, run it in the same PR as the rename. Keep a deprecated alias for one release cycle, then remove.

Why do my dark-mode overrides need !important?

Because component CSS beats :root tokens by specificity. Move overrides to :root[data-theme='dark'] at the document level, never inside component CSS.

How do I keep web and iOS tokens in sync?

Generate both outputs in CI from one JSON source — fail the build if tokens.css and tokens.swift diverge. Never hand-edit platform outputs.