Design System Tokens skill for Claude Code
Semantic tokens, so a new theme is one file. Claude starts by grepping your codebase for the hex codes and pixel values already in use, clusters them into primitive scales, then layers semantic role tokens on top so components never reference raw colors. It picks a source format that fits your stack, wires Style Dictionary when you ship to several platforms, and adds lint rules that stop new hard-coded values.
- name
design-system-tokens - size239 lines · ≈2,150 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Builds and audits design token systems — primitive, semantic, and component tiers for color, type, space, radius, shadow, and motion — in DTCG JSON, CSS custom properties, or Tailwind v4 @theme, plus theming and component API conventions. Use when starting a design system, replacing hard-coded hex and pixel values, adding themes or brands, wiring Style Dictionary, or reviewing why components drift visually. Try asking
- We have 40 different grays in this codebase. Turn them into a proper token system
- Set up DTCG tokens with Style Dictionary that output CSS variables and a dark theme
- Review our Tailwind @theme block and tell me which tokens should be semantic
Use it when
- Starting a design system or component library
- Components drift because values are hard-coded
- Adding a second theme, brand, or white-label variant
- Syncing Figma Variables with code
Skip it when
- You only need a palette with accessible contrast, use color-systems instead
- The work is Tailwind configuration and utility discipline, use tailwind-css instead
How to check the result
Grep finds raw hex values only in the token source file, and toggling the theme attribute restyles every surface with all text pairs passing contrast.
The common mistake it prevents
Naming semantic tokens after their current appearance, like text-gray, which becomes a lie the moment a dark theme maps it to a light value.
A token system is done when every visual value in product code comes from a semantic
token, a new theme is a single file that remaps semantic tokens, and nobody needs to
ask “which gray?” A token file full of blue-500 used directly in components is a
palette, not a system.
Ground rules
- Three tiers, strictly layered: primitive (raw values) → semantic (intent) → component (optional, only where a component needs its own knob). Components read semantic or component tokens, never primitives.
- Name by role, not appearance:
color.text.muted, notcolor.gray-600. Appearance names belong only in the primitive tier. - Every token has exactly one source of truth (one JSON tree or one CSS file); all other formats are generated.
- Fewer tokens beat more. If two semantic tokens always resolve to the same value in every theme, merge them.
- Never add a token for a one-off. A value used once is a local style, not a token.
Workflow
- [ ] 1. Inventory existing values (grep the codebase)
- [ ] 2. Cluster into primitive scales
- [ ] 3. Define semantic roles and map them per theme
- [ ] 4. Pick the source format and build pipeline
- [ ] 5. Migrate components tier by tier
- [ ] 6. Enforce with lint and document
1. Inventory before designing
Measure what exists; the drift tells you which scales are needed.
# distinct hex colors and how often each appears
grep -rhoE '#[0-9a-fA-F]{3,8}\b' src | tr 'A-F' 'a-f' | sort | uniq -c | sort -rn | head -40
# pixel values in CSS/TSX (spacing and radius candidates)
grep -rhoE '\b[0-9]+px\b' src | sort | uniq -c | sort -rn | head -40
# font sizes
grep -rhoE 'font-size:\s*[^;]+' src | sort | uniq -c | sort -rn
Typical finding: 30+ grays that should be 10–12, and spacing values like 13px, 15px, 18px that should snap to a scale. Record the mapping old → new for the migration.
2. Primitive scales (recommended defaults)
| Category | Default scale | Notes |
|---|---|---|
| Space | 4px base: 0, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128 | Name by step (space.4 = 16px) or t-shirt; never by pixel value in semantic tier |
| Radius | 0, 2, 4, 6, 8, 12, 16, 9999 | Pick 2–3 for product use; full for pills |
| Font size | Modular ratio 1.2 (UI) or 1.25 (marketing) from 16px | See typography-scale |
| Line height | 1.1 (display), 1.25 (headings), 1.5 (body) | Unitless values |
| Font weight | 400, 500, 600, 700 | Only weights actually loaded |
| Color | 11–12 steps per hue in OKLCH (50–950) | See color-systems |
| Shadow | 3–4 elevations max | Each a full box-shadow value |
| Duration | 100, 150, 200, 300, 500 ms | See ui-motion |
| Easing | standard, enter (decelerate), exit (accelerate) | Cubic-bezier values |
| Z-index | base 0, dropdown 100, sticky 200, overlay 300, modal 400, toast 500 | Named layers prevent 9999 wars |
3. Semantic roles
Start with this minimal set and add only when a real component needs it:
color.bg.canvas page background
color.bg.surface cards, panels
color.bg.raised popovers, menus
color.bg.sunken inputs, wells
color.text.default body text
color.text.muted secondary text (must still hit 4.5:1)
color.text.inverse text on accent fills
color.border.default hairlines
color.border.strong input borders (3:1 against bg for UI components)
color.accent.default primary action fill
color.accent.hover
color.accent.text accent-colored text on canvas (4.5:1)
color.focus.ring
color.status.{danger,warning,success,info}.{bg,text,border}
Themes remap semantic tokens only. Light and dark are two mappings of the same names.
4. Pick the source format
| Situation | Source of truth | Build |
|---|---|---|
| Tailwind v4 project, single platform | CSS @theme block | None needed |
| Web only, no Tailwind | tokens.css with custom properties | None needed |
| Multiple platforms (web + iOS/Android) or Figma sync | DTCG JSON ($value, $type) | Style Dictionary v4+ |
| Figma Variables are the source | Export to DTCG JSON, then as above | Plugin/REST export + Style Dictionary |
DTCG JSON (the W3C Design Tokens Community Group format; first stable spec 2025.10):
{
"color": {
"blue": {
"600": { "$type": "color", "$value": "oklch(0.55 0.18 255)" }
},
"accent": {
"default": { "$type": "color", "$value": "{color.blue.600}" }
}
},
"space": {
"4": { "$type": "dimension", "$value": "16px" }
}
}
Style Dictionary build (v4 is ESM, reads DTCG natively):
// sd.config.mjs
export default {
source: ["tokens/**/*.json"],
platforms: {
css: {
transformGroup: "css",
buildPath: "dist/",
files: [{ destination: "tokens.css", format: "css/variables",
options: { outputReferences: true } }],
},
},
};
npx style-dictionary build --config sd.config.mjs
outputReferences: true keeps var(--color-blue-600) references in the output so
theme overrides cascade instead of being baked in.
Tailwind v4 CSS-first tokens:
@import "tailwindcss";
@theme {
--color-blue-600: oklch(0.55 0.18 255);
--spacing: 0.25rem; /* drives p-4 = 1rem, gap-6 = 1.5rem */
--radius-md: 0.375rem;
--font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
}
@layer base {
:root { --accent: var(--color-blue-600); --text-muted: oklch(0.45 0.02 255); }
[data-theme="dark"] { --accent: oklch(0.72 0.15 255); --text-muted: oklch(0.72 0.02 255); }
}
Use @theme inline when a theme variable references another variable, so utilities
resolve the reference at use time rather than at definition time.
5. Migrate
- Ship the token file alongside existing styles; change nothing visible.
- Replace values in shared primitives first (Button, Input, Card, Text), then pages.
- Snap off-scale values to the nearest step; record any intentional exceptions in a comment with the reason.
- Use visual regression screenshots per component to catch unintended shifts (see
visual-regression).
6. Enforce
- Stylelint: ban raw colors outside the token file.
{
"rules": {
"color-no-hex": true,
"declaration-property-value-disallowed-list": {
"/^(margin|padding|gap)/": ["/\\d+px/"]
}
},
"overrides": [{ "files": ["src/styles/tokens.css"], "rules": { "color-no-hex": null } }]
}
- Tailwind: forbid arbitrary values like
p-[13px]andtext-[#333]in review; grep for-\[in class strings during CI.
Component API conventions
- Variant props describe intent:
variant="primary|secondary|ghost|danger",size="sm|md|lg". Nevercolor="blue". - Sizes map to token steps, e.g.
sm= height 32 / padding-x 12 / text 14;md= 40 / 16 / 14;lg= 48 / 20 / 16. Keep touch targets at least 24×24 CSS px (WCAG 2.2 AA) and 44×44 for primary mobile actions. - Expose a component token only when consumers legitimately vary it
(
--button-radius), not for every property. - Document each component with: props table, tokens it reads, do/don’t examples, accessibility notes.
Gotchas
- Semantic names that encode a value (
color.text.gray) break the moment dark mode makes it not gray. - Alpha-based tokens (
rgba(0,0,0,.6)text) change contrast per background; prefer opaque colors for text. - Generated CSS without
outputReferenceshard-codes values, so[data-theme]overrides silently stop working for derived tokens. - Tokens for every Figma style produce 400-token files nobody can choose from. Cap semantic color tokens at roughly 30–40 for a product UI.
- Renaming a published token is a breaking change; deprecate with an alias for one release cycle.
Verification
grep -rnE '#[0-9a-fA-F]{3,8}\b' src --include='*.{css,tsx,jsx,vue,svelte}'returns only the token source file.- Toggling
data-themeon<html>changes every surface with no hard-coded leftovers (inspect a screenshot of each main page in both themes). - Every semantic text/background pair passes WCAG contrast in every theme (run the
checker in
color-systems). - The build output diff is reviewed whenever tokens change.
Output contract
When delivering a token system, provide:
tokens/
primitives.json | primitives.css raw scales
semantic.light.json role → primitive mapping
semantic.dark.json
README.md tiers, naming rules, how to add a token
dist/tokens.css generated (if a build exists)
MIGRATION.md old value → token map, exceptions list About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.