Skip to content
Back to skills

Design Tokens Scaffolding

ASecurity

Scaffold a token system from brand spec to token JSON to CSS variables to framework theme (Tailwind / Shadcn / CSS-in-JS / Theme UI). Includes naming convention, scale design (color / spacing / typography / radius / shadow / motion), light/dark mode, semantic vs primitive tokens, and the dual-mode build pipeline. Reach for this skill when launching a new design system or when an existing system has "design drift" between Figma and code. Used by `visual-designer` (primary) + `frontend-implemen...

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentstypescriptgorubyreactexpressspringgitfrontend

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill design-tokens-scaffolding --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Design Tokens Scaffolding?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Design Tokens Scaffolding
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-design-tokens-scaffolding/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-design-tokens-scaffolding)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: design-tokens-scaffolding
description: Scaffold a token system from brand spec to token JSON to CSS variables to framework theme (Tailwind / Shadcn / CSS-in-JS / Theme UI). Includes naming convention, scale design (color / spacing / typography / radius / shadow / motion), light/dark mode, semantic vs primitive tokens, and the dual-mode build pipeline. Reach for this skill when launching a new design system or when an existing system has "design drift" between Figma and code. Used by `visual-designer` (primary) + `frontend-implementer`.
---

# Skill: design-tokens-scaffolding

**Purpose:** Scaffold a design token system end-to-end: brand spec → token JSON → CSS variables → framework theme. Used by `visual-designer` (primary) and `frontend-implementer` (token-to-code wiring).

Tokens are the contract between design and engineering. Without them, the design system is a screenshot library and code accumulates hardcoded hex values. With them, a color update propagates everywhere in one PR. The discipline is to model **decisions** (primitives) separately from **uses** (semantics), and to make the pipeline one-way: design tool → JSON → code.

## When to use

- Launching a new design system from scratch
- Existing system has visible drift between Figma and code (different blues, different spacing)
- Dark-mode rollout (semantic tokens are how this becomes tractable)
- Tailwind / Shadcn migration where the team wants brand-specific theming
- Component-library refactor — pre-work before the components themselves change

## 1. Primitive vs semantic tokens (the non-negotiable distinction)

This is the single highest-leverage idea in a token system. Two layers:

**Primitives** — raw values, named by what they are:

```json
{
  "color": {
    "gray": {
      "50":  "#fafafa",
      "100": "#f4f4f5",
      "500": "#71717a",
      "900": "#18181b"
    },
    "brand": {
      "500": "#3b82f6"
    }
  }
}
```

**Semantics** — use-cases that consume primitives, named by what they do:

```json
{
  "color": {
    "background": {
      "default": "{color.gray.50}",
      "subtle":  "{color.gray.100}",
      "inverse": "{color.gray.900}"
    },
    "text": {
      "default": "{color.gray.900}",
      "muted":   "{color.gray.500}",
      "inverse": "{color.gray.50}"
    },
    "accent": {
      "default": "{color.brand.500}"
    }
  }
}
```

**Components consume semantic tokens only.** Never `bg-gray-50` in a component; always `bg-background-default`. The button doesn't know what color it is — it knows it's a button.

This is the discipline that makes dark mode tractable, makes brand refreshes a one-PR change, and prevents drift.

## 2. Naming conventions

- **Avoid Tailwind-like appearance names in semantics** — `blue-500` is fine as a primitive; in components, use `primary` or `accent`, not `blue`. When the brand color stops being blue in v2, you don't want to rename `bg-blue-500` everywhere.
- **Role-based, not appearance-based** — `danger` not `red`, `success` not `green`, `muted` not `gray`
- **Hierarchical dots** — `color.text.default`, `color.text.muted`, `color.text.inverse`. Maps cleanly to JSON, CSS custom properties, and Tailwind config.
- **Surface terms consistent** — `background` / `foreground` / `border` / `text` / `accent`. Match Radix / Shadcn's vocabulary; the team already knows it.
- **State suffixes** — `-hover`, `-active`, `-disabled`, `-focus` as a suffix on the role (`accent-hover`, not `hover-accent`)

## 3. Scale design

Every visual property needs a scale. Scales constrain choice, which is what makes the system feel coherent.

### Color ramps

- **9-step or 11-step** per color family (50–900 or 50–950). Tailwind's pattern works because each step has computed contrast meaning: `500` is the typical mid-tone, `700–900` for text on light backgrounds, `50–200` for backgrounds behind dark text.
- **Computed contrast** — for each step, document the WCAG contrast it provides against `gray.50` and `gray.900`. Surface the failing pairs.
- **Brand + neutral + 3–4 functional** (success / warning / danger / info). No more. A 9th accent color is a design smell.

### Spacing

- **Geometric (`4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96`)** — the 8pt system with `4` for fine adjustments. Modern reference.
- **No off-scale values in production** — if a component needs `13px`, the scale is wrong or the component is wrong.

### Typography

- **Modular type scale** — base × ratio. Common: base 16, ratio 1.25 (major third) for marketing, 1.2 (minor third) for dense UI.
- **8–10 named steps** — `display-xl`, `display-lg`, `h1`, `h2`, `h3`, `h4`, `body-lg`, `body`, `body-sm`, `caption`. Don't ship 20 steps.
- **Line-height paired with size** — heading line-heights tight (1.1–1.2), body line-heights comfortable (1.5–1.6). Encode as the token, not as a separate decision per component.
- **Font-weight palette** — 400 / 600 / 700 typical; never load every weight. Variable fonts let you express weight as a number without N font files.

### Radius

- `none` / `sm` (4px) / `md` (8px) / `lg` (12px) / `xl` (16px) / `full`. Stops there.

### Shadow / elevation

- `0` / `1` (1px subtle) / `2` (card) / `3` (popover) / `4` (modal). Each step is a pre-composed `box-shadow` value; no per-component custom shadows.

### Motion

- **Duration scale** — `instant` (0ms — important for reduced-motion), `fast` (100ms), `default` (200ms), `slow` (300ms), `slower` (500ms)
- **Easing scale** — `ease-out` (entries), `ease-in` (exits), `ease-in-out` (through-states), `spring` (Framer / Motion). Never linear except for indeterminate loading.
- **`prefers-reduced-motion: reduce` is a semantic-token re-pointing exercise** — all durations point to `instant`, all transforms point to opacity-only fades.

## 4. Light / dark mode

Dark mode works if and only if the system is semantic. Mechanics:

- **Primitives don't change** — `gray.50` is still `#fafafa` in dark mode. Primitives are facts.
- **Semantics swap** — `color.background.default` points to `gray.50` in light, `gray.900` in dark. `color.text.default` flips. `color.accent` may stay the same or get a brighter variant.
- **`color-scheme: light dark` declared** on `:root` so native UI (scrollbars, form controls) respect the mode
- **System preference + override** — respect `prefers-color-scheme` by default; let the user override (toggle, persisted in localStorage)
- **Test every component in both modes** — Storybook should have a mode toggle on every story
- **Brand colors don't always survive dark mode** — a vivid brand color on a dark background often needs a luminosity-boosted variant. Document this as a primitive (`brand.500-dark`) and re-point the semantic.

## 5. Token JSON format

Two emerging standards in 2026:

- **Style Dictionary** — Amazon's tool, JSON format, very mature
- **W3C Design Tokens Community Group draft format** — converging standard, supported by Figma Tokens / Tokens Studio, Specify, Knapsack, Supernova

Pick W3C-aligned format for forward compatibility:

```json
{
  "color": {
    "background": {
      "default": {
        "$value": "{color.gray.50}",
        "$type": "color",
        "$description": "Page-level background, light mode"
      }
    }
  }
}
```

The `$value`, `$type`, `$description` triple is the W3C convention. References use `{path.to.token}` syntax.

**Optional cross-agent export.** If the tokens need to stay legible to a coding agent operating
outside this repo's build tooling (a different session, a different host), an emerging
alpha-stage format — `DESIGN.md` (Google Labs) — exports this same W3C DTCG token set to a single
git-committed file alongside a Tailwind config. It is an optional additional artifact, not a
replacement for the pipeline below — see
[`../../knowledge/design-md-token-interop.md`](../../knowledge/design-md-token-interop.md).

## 6. Build pipeline

The pipeline is the contract:

```
Figma (Tokens Studio plugin)
   ↓ publishes
tokens/tokens.json  ← source of truth, in the repo
   ↓ Style Dictionary build
build/css/tokens.css          (CSS custom properties)
build/tailwind/tokens.cjs     (Tailwind theme config)
build/ts/tokens.ts            (TypeScript typed export — for CSS-in-JS / inline)
   ↓ consumed by
components/*  ← only via semantic CSS variables or Tailwind classes
```

Concrete steps:

1. **Designers edit in Figma**, publish via Tokens Studio plugin to a JSON file
2. **CI checks the JSON** — schema valid, no orphan references, no naming-convention violations
3. **Style Dictionary builds the platform outputs** — CSS variables, Tailwind config, TS types, iOS / Android if applicable
4. **The framework theme consumes the built outputs** — `tailwind.config.cjs` imports the generated config, CSS-in-JS theme provider reads the TS export
5. **No hand-edits in `build/`** — generated artifact, gitignored or generated on `pnpm build`

### Tailwind specifically

```js
// tailwind.config.cjs
const tokens = require('./build/tailwind/tokens.cjs');
module.exports = {
  theme: {
    colors: tokens.color,           // semantic names — accent, background, text
    spacing: tokens.spacing,
    fontSize: tokens.fontSize,
    borderRadius: tokens.radius,
    boxShadow: tokens.shadow,
    extend: {}
  }
};
```

The team uses `bg-accent` not `bg-blue-500`. The Tailwind class set **becomes** the semantic vocabulary.

### Shadcn / Radix specifically

Shadcn ships with semantic CSS custom properties (`--background`, `--foreground`, `--primary`, etc.) that already match the discipline. The job is to re-point these to your generated tokens, not to invent a parallel system.

**2026 primitive-layer note:** shadcn/ui now defaults new projects to **Base UI** (MUI's headless-primitive library, staffed by several ex-Radix engineers, hit stable v1.0 December 2025) rather than Radix, as Radix's release cadence slowed following its 2026 acquisition by WorkOS — most visible on complex components (Combobox/multi-select). Either primitive still fits this skill's semantic-token discipline unchanged; this only affects which headless-primitive package sits under the generated component layer. See [`../../knowledge/design-sources/additional-sources.md`](../../knowledge/design-sources/additional-sources.md).

### Worked example — tokens as an independently versioned package

**[Shopify Polaris tokens](https://github.com/Shopify/polaris-tokens)** (`@shopify/polaris-tokens`, docs at <https://polaris.shopify.com/>) is a real, currently-maintained case study of §6's discipline taken to its logical conclusion: tokens ship as their **own versioned package** (npm + a Ruby gem), consumed independently of the component library, in a documented monorepo split (`polaris-tokens` / `polaris-react` / docs site). Point to this when a team is deciding whether tokens deserve their own package boundary rather than living inside the component library's build — Polaris is the concrete "yes, and here's how" answer.

## 7. Audit for drift

Once the system ships, drift is the failure mode:

- **Figma-published tokens vs code tokens** — quarterly diff. CI step: parse the Figma export, parse the built CSS, fail on any drift.
- **Hardcoded values in code** — grep for hex literals outside the `tokens/` directory and the generated `build/` outputs. Plugin's anti-pattern hook catches some of this.
- **Off-scale spacing** — grep for `padding: 13px` etc. Lint rule that allows only token values.
- **Component-level color overrides** — `style={{ color: '#ff0000' }}` in JSX. Same lint.
- **Designer's "exception" colors** — every additional brand color or one-off shade is debt. Audit and consolidate.

## Hygiene checklist

- [ ] Primitive layer defined separately from semantic layer
- [ ] Components only reference semantic tokens
- [ ] Naming uses roles, not appearances (`accent` not `blue`)
- [ ] All scales geometric (no off-scale spacing, no random font sizes)
- [ ] Color ramps document computed WCAG contrast pairs
- [ ] Dark-mode semantics defined; primitives unchanged
- [ ] `prefers-reduced-motion` re-points motion semantics to `instant`
- [ ] Token JSON in W3C-draft format (or Style Dictionary)
- [ ] Build pipeline one-way (Figma → JSON → platform outputs); no hand-edited build artifacts
- [ ] Storybook (or equivalent) renders every component in both modes
- [ ] Drift audit scheduled (Figma vs code, quarterly)
- [ ] Lint rule for hardcoded hex / off-scale values in components

## Anti-patterns

- **Cosmetics in shared tokens** — `color.button-hover-pink-special` is a one-component override masquerading as a token. Tokens are decisions used in ≥ 2 places.
- **Hex sprinkled in components** — `style={{ color: '#3b82f6' }}` or `className="text-[#3b82f6]"` (Tailwind arbitrary value). Both are anti-patterns.
- **Appearance-based naming in semantics** — `text-blue-500` in a component. Rename to `text-accent` immediately.
- **Component-level dark-mode overrides** — `dark:bg-gray-900` everywhere. Means the semantic layer is missing.
- **Filter-based dark mode** — `filter: invert(1) hue-rotate(180deg)`. Visually catastrophic on images and brand colors; immediate red flag.
- **Tailwind defaults shipped unchanged** — `text-blue-500` from Tailwind's default palette. Either replace the theme, or accept that the team has no brand.
- **20-step type scale** — designers indulging. Consolidate.
- **Tokens defined in code, mirrored in Figma manually** — drift is guaranteed. Pipeline must be one-way.
- **No motion tokens** — every animation invents its own duration / easing. Inconsistency at the smallest scale.
- **Token file with no `$description` fields** — six months later, no one remembers what `color.surface.subtle-inverse-2` is for.

## See also

- Skill: [`../design-system-audit/SKILL.md`](../design-system-audit/SKILL.md)
- Skill: [`../accessibility-review/SKILL.md`](../accessibility-review/SKILL.md) — color contrast checks live here
- Template: [`../../templates/design-system-spec.md`](../../templates/design-system-spec.md)
- Knowledge: [`../../knowledge/design-references.md`](../../knowledge/design-references.md)
- Knowledge: [`../../knowledge/design-md-token-interop.md`](../../knowledge/design-md-token-interop.md) — the optional `DESIGN.md` cross-agent export
- Agent: [`../../agents/visual-designer.md`](../../agents/visual-designer.md)
- Agent: [`../../agents/frontend-implementer.md`](../../agents/frontend-implementer.md)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…