Design Tokens skill for Codex

Name roles once, theme every platform from one source. Codex counts the hard-coded hex values and pixel sizes first, then builds a two-tier token set with a strict naming grammar, writes it in DTCG JSON, and generates CSS variables, JS and native outputs with Style Dictionary. Themes swap semantic aliases only, Tailwind v4 reads them through @theme inline, and CI catches raw values that sneak back in.

  • Design & UX
  • $design-tokens
  • 227 lines
  • 2 files
  • ~2,250 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- design-tokens

Installs to ~/.agents/skills/design-tokens/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $design-tokens, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs design-token architecture - primitive, semantic and component tiers, naming grammar, DTCG JSON with $value/$type and aliases, Style Dictionary builds to CSS variables, Tailwind v4 @theme, iOS and Android, and multi-theme or multi-brand output with Figma Variables modes. Use when creating or refactoring tokens, naming color or spacing variables, setting up Style Dictionary, syncing Figma variables to code, adding dark mode or a second brand through tokens, or auditing hard-coded values.

Use it when

  • Creating a token system or refactoring hard-coded colors and spacing
  • Supporting dark mode, high contrast or multiple brands from one codebase
  • Configuring Style Dictionary or the DTCG token format
  • Keeping Figma Variables and code variables in sync

Use something else when

  • Choosing the actual palette values and contrast pairs: use color-systems
  • Building and governing the components that consume tokens: use design-systems

How to check the result

The token build runs with no unresolved references, a search for hex values in components returns nothing, and toggling data-theme changes every surface.

The mistake it prevents

Naming tokens after values (blue-500, space-16px) so a rebrand or dark theme forces renames across the whole codebase.

~/.agents/skills/design-tokens/SKILL.md

--- name: design-tokens description: Designs design-token architecture - primitive, semantic and component tiers, naming grammar, DTCG JSON with $value/$type and aliases, Style… license: MIT ---

Tokens are right when a theme or brand change touches only token files, components reference roles (color.text.muted) never raw values (#64748b, gray-500), and one source builds every platform output in CI. Tokens named after their values are just a palette with extra steps.

Before changing anything

  • Read AGENTS.md, then find the current sources of truth: tokens/, *.tokens.json, tailwind.config.*, @theme blocks, :root variable files, Figma Variables exports.
  • Measure hard-coded values to size the job:
rg -o --glob '*.{css,scss,tsx,jsx,vue,svelte}' '#[0-9a-fA-F]{3,8}\b' src | wc -l
rg -o --glob '*.{css,scss}' '\b[0-9]+px\b' src | sort | uniq -c | sort -nr | head -20
rg -o --glob '*.{tsx,jsx}' '\b(bg|text|border)-(gray|slate|zinc|blue|red)-[0-9]{2,3}\b' src | sort | uniq -c | sort -nr | head
  • Note every platform that must consume tokens: web CSS, Tailwind, React Native, iOS, Android, email, charts.

Tier model

TierExample nameValueWho references it
Primitive (reference)color.blue.600oklch(0.55 0.18 255)Only semantic tokens
Semantic (system)color.bg.action.default{color.blue.600}Components, patterns, app code
Component (optional)button.primary.bg{color.bg.action.default}That component only
  • Default to two tiers. Add component tokens only for components that brands or themes restyle independently; otherwise they multiply names without adding control.
  • Themes and brands swap the semantic layer’s aliases; primitives stay stable.
  • App code must not reference primitives. Enforce with lint (stylelint or a grep in CI).

Naming grammar

<category>.<property>.<role>[.<variant>][.<state>], lowercase, dot-separated in JSON, kebab-case in CSS output:

CategoryNames
Colorcolor.text.default, color.text.muted, color.text.on-action, color.bg.surface, color.bg.surface-raised, color.bg.action.default, color.bg.action.hover, color.border.subtle, color.border.focus, color.status.danger.text, color.status.danger.bg
Spacespace.1 to space.12 on a fixed scale (4 px base), plus aliases like space.inset.card only if needed
Sizesize.control.sm/md/lg, size.icon.sm/md
Radiusradius.control, radius.container, radius.full
Typefont.family.display/text/mono, font.size.100..900, font.weight.regular/medium/bold, line-height.tight/normal
Motionduration.fast/base/slow, easing.standard/enter/exit
Elevationshadow.raised, shadow.overlay, z.dropdown/modal/toast

Rules: name the role, not the hue (danger not red); pair every background role with its text role (bg.action.default + text.on-action); states are suffixes, not separate categories; never encode a value in a semantic name (space.16px).

Source format (DTCG)

Write tokens in the W3C Design Tokens Community Group format: $value, $type, $description, and {path.to.token} aliases. Group-level $type is inherited.

{
  "color": {
    "$type": "color",
    "blue": { "600": { "$value": "#1d5fd6" }, "700": { "$value": "#174bb0" } },
    "neutral": { "0": { "$value": "#ffffff" }, "900": { "$value": "#111418" } }
  }
}
{
  "color": {
    "$type": "color",
    "bg": {
      "surface": { "$value": "{color.neutral.0}" },
      "action": {
        "default": { "$value": "{color.blue.600}" },
        "hover": { "$value": "{color.blue.700}" }
      }
    },
    "text": { "default": { "$value": "{color.neutral.900}" } }
  }
}

The stable DTCG spec (2025.10) defines colors and dimensions as structured objects (colorSpace/components, value/unit). Tool support varies; check what your Style Dictionary version and Figma plugin accept before adopting the object form, and keep string values ("#1d5fd6", "16px") if the toolchain does not parse objects yet. A token cannot also be a group in DTCG, which is why states are sibling leaves (action.default, action.hover) rather than children of a token.

File layout:

tokens/
  primitive/color.json  space.json  type.json
  semantic/light.json   dark.json   brand-b.light.json

Build with Style Dictionary (v4+, ESM)

npm i -D style-dictionary (needs network/approval in a sandbox). Then:

// build-tokens.mjs
import StyleDictionary from "style-dictionary";

const themes = { light: ":root", dark: '[data-theme="dark"]' };

for (const [theme, selector] of Object.entries(themes)) {
  const sd = new StyleDictionary({
    source: ["tokens/primitive/**/*.json", `tokens/semantic/${theme}.json`],
    platforms: {
      css: {
        transformGroup: "css",
        buildPath: "dist/css/",
        files: [
          ...(theme === "light"
            ? [{ destination: "primitives.css", format: "css/variables",
                 filter: (t) => t.filePath.includes("primitive/") }]
            : []),
          { destination: `${theme}.css`, format: "css/variables",
            filter: (t) => t.filePath.includes("semantic/"),
            options: { selector, outputReferences: true } },
        ],
      },
      ts: {
        transformGroup: "js",
        buildPath: "dist/js/",
        files: [{ destination: `${theme}.js`, format: "javascript/es6" }],
      },
    },
  });
  await sd.buildAllPlatforms();
}
  • outputReferences: true keeps var(--color-blue-600) in semantic output, so devtools show the alias chain. The primitives file must load first.
  • Custom transforms in v4 use StyleDictionary.registerTransform({ name, type: "value", filter, transform }) (v3 used matcher/transformer). With DTCG input, read token.$value.
  • Add "tokens": "node build-tokens.mjs" to scripts and run it in CI; fail the build if git diff --exit-code dist/ shows uncommitted output, or do not commit dist/ at all.

Consuming tokens

Tailwind v4: map semantic CSS variables into the theme so utilities follow the theme:

@import "tailwindcss";
@import "./dist/css/primitives.css";
@import "./dist/css/light.css";
@import "./dist/css/dark.css";

@theme inline {
  --color-surface: var(--color-bg-surface);
  --color-action: var(--color-bg-action-default);
  --color-fg: var(--color-text-default);
}

bg-surface, bg-action and text-fg now switch with data-theme. Use inline so utilities reference the variable instead of a value frozen at build time.

Native: Style Dictionary ships ios-swift and compose transform groups; build them as extra platforms. React Native: output a JS object and wrap in a theme provider.

Figma Variables

  • Collections map to tiers (Primitives, Semantic), modes map to themes (Light, Dark).
  • Keep names identical to token paths with / as the separator (color/bg/action).
  • Sync options: Tokens Studio plugin to Git, a Variables export plugin, or the REST Variables API (GET /v1/files/:key/variables/local, Enterprise plans only).
  • Decide one direction of truth (usually Git) and make the other read-only by process.

Workflow

- [ ] 1. Inventory hard-coded values and existing variables
- [ ] 2. Define primitives from the values actually in use (dedupe near-duplicates)
- [ ] 3. Define semantic roles from UI needs (text, bg, border, status, focus)
- [ ] 4. Write DTCG files and the build script; generate outputs
- [ ] 5. Replace hard-coded values in components with semantic tokens
- [ ] 6. Add a second theme to prove the architecture (dark or high contrast)
- [ ] 7. Add CI: build, lint for raw values, contrast check of text/bg pairs

Gotchas

  • Renaming or deleting a token is a breaking change for every consumer. Alias the old name to the new one for a release, mark it deprecated in $description, then remove.
  • Translucent variants: Tailwind v4 compiles bg-action/50 to color-mix(), but v3 needs channel-only variables with <alpha-value>. In plain CSS write color-mix(in oklch, var(--color-bg-action-default) 50%, transparent).
  • Semantic tokens that only exist for one screen are component tokens in disguise.
  • Spacing tokens in px for type-relative spacing break with user font scaling; use rem for space and type.
  • A theme that only flips colors misses elevation, borders and images; see dark-mode-theming.

Verification

  • Build runs clean with no unresolved reference warnings.
  • rg for hex values and palette utility classes in components returns zero (or an agreed allowlist).
  • Switch data-theme in the browser: every surface and text changes, nothing stays stuck on a light value.
  • Every text.* on bg.* pair used in components passes 4.5:1 in every theme.

Output contract

**Tiers:** primitive (n tokens), semantic (n), component (n or none)
**Naming grammar:** <pattern with 3 examples>
**Sources:** tokens/... **Build:** `npm run tokens` -> dist/css, dist/js, ...
**Themes:** light, dark (+ brands)
**Replaced:** <n> hard-coded values across <n> files; remaining allowlist: <...>
**Breaking renames:** <old -> new, deprecation release>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Design Tokens"
  short_description: "Name roles once, theme every platform from one source"
  default_prompt: "$design-tokens Set up design tokens with Style Dictionary for our web and iOS apps"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc