Here's a three-layer token set for a SaaS dashboard primary button. It follows the skill's rules: no raw values in components, and the semantic layer is the theme switch.
/* ==========================================================
LAYER 1 — PRIMITIVE: raw values, no meaning attached
========================================================== */
:root {
/* Blue scale */
--color-blue-50: #EFF6FF;
--color-blue-500: #3B82F6;
--color-blue-600: #2563EB;
--color-blue-700: #1D4ED8;
--color-blue-800: #1E40AF;
/* Neutral scale */
--color-gray-50: #F9FAFB;
--color-gray-100: #F3F4F6;
--color-gray-200: #E5E7EB;
--color-gray-400: #9CA3AF;
--color-gray-900: #111827;
--color-white: #FFFFFF;
/* Spacing (4px base) */
--space-2: 0.5rem;
--space-4: 1rem;
/* Radius, type, motion */
--radius-md: 0.375rem;
--font-size-sm: 0.875rem;
--font-weight-medium: 500;
--duration-fast: 150ms;
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
/* Shadow */
--shadow-sm: 0 1px 2px rgb(17 24 39 / 0.08);
--shadow-md: 0 4px 8px rgb(17 24 39 / 0.12);
}
/* ==========================================================
LAYER 2 — SEMANTIC: purpose-based aliases (theme layer)
========================================================== */
:root {
--color-primary: var(--color-blue-600);
--color-primary-hover: var(--color-blue-700);
--color-primary-active: var(--color-blue-800);
--color-on-primary: var(--color-white);
--color-muted: var(--color-gray-100);
--color-muted-foreground: var(--color-gray-400);
--color-muted-border: var(--color-gray-200);
--color-focus-ring: var(--color-blue-500);
--control-padding-y: var(--space-2);
--control-padding-x: var(--space-4);
--control-radius: var(--radius-md);
--elevation-low: var(--shadow-sm);
--elevation-mid: var(--shadow-md);
--transition-interactive: var(--duration-fast) var(--ease-standard);
}
/* Dark theme: only semantic values are remapped */
[data-theme="dark"] {
--color-primary: var(--color-blue-500);
--color-primary-hover: var(--color-blue-600);
--color-primary-active: var(--color-blue-700);
--color-on-primary: var(--color-gray-900);
--color-muted: var(--color-gray-900);
--color-muted-foreground: var(--color-gray-400);
--color-muted-border: var(--color-gray-400);
}
/* ==========================================================
LAYER 3 — COMPONENT: button-specific, references semantic only
========================================================== */
:root {
/* Default */
--button-primary-bg: var(--color-primary);
--button-primary-fg: var(--color-on-primary);
--button-primary-border: transparent;
--button-primary-shadow: var(--elevation-low);
/* Hover */
--button-primary-bg-hover: var(--color-primary-hover);
--button-primary-fg-hover: var(--color-on-primary);
--button-primary-shadow-hover: var(--elevation-mid);
/* Active (pressed) */
--button-primary-bg-active: var(--color-primary-active);
--button-primary-shadow-active: none;
/* Disabled */
--button-primary-bg-disabled: var(--color-muted);
--button-primary-fg-disabled: var(--color-muted-foreground);
--button-primary-border-disabled: var(--color-muted-border);
--button-primary-shadow-disabled: none;
/* Shape & type */
--button-primary-padding: var(--control-padding-y) var(--control-padding-x);
--button-primary-radius: var(--control-radius);
--button-primary-font-size: var(--font-size-sm);
--button-primary-font-weight: var(--font-weight-medium);
--button-primary-focus-ring: var(--color-focus-ring);
}
Usage: the component rule consumes only layer-3 tokens.
.btn-primary {
padding: var(--button-primary-padding);
font-size: var(--button-primary-font-size);
font-weight: var(--button-primary-font-weight);
border: 1px solid var(--button-primary-border);
border-radius: var(--button-primary-radius);
background: var(--button-primary-bg);
color: var(--button-primary-fg);
box-shadow: var(--button-primary-shadow);
transition: background var(--transition-interactive),
box-shadow var(--transition-interactive);
cursor: pointer;
}
.btn-primary:hover:not(:disabled) {
background: var(--button-primary-bg-hover);
color: var(--button-primary-fg-hover);
box-shadow: var(--button-primary-shadow-hover);
}
.btn-primary:active:not(:disabled) {
background: var(--button-primary-bg-active);
box-shadow: var(--button-primary-shadow-active);
}
.btn-primary:focus-visible {
outline: 2px solid var(--button-primary-focus-ring);
outline-offset: 2px;
}
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] {
background: var(--button-primary-bg-disabled);
color: var(--button-primary-fg-disabled);
border-color: var(--button-primary-border-disabled);
box-shadow: var(--button-primary-shadow-disabled);
cursor: not-allowed;
}
State matrix (matches the skill's spec pattern):
| Property | Default | Hover | Active | Disabled |
|---|---|---|---|---|
| Background | blue-600 | blue-700 | blue-800 | gray-100 |
| Text | white | white | white | gray-400 |
| Border | none | none | none | gray-200 |
| Shadow | sm | md | none | none |
Notes
- Layer rule: components reference semantic tokens, and semantic tokens reference primitives. Nothing skips a layer.
- Rebranding: change
--color-primaryand the button follows, with no component edits. - Dark theme: the dark block flips
--color-on-primaryto dark text because blue-500 with white text is only about 3.7:1 contrast. Verify contrast against your final palette. - Disabled state: it has a visible border and muted text, so it doesn't rely on color alone.
I haven't run the skill's generate-tokens.cjs or validate-tokens.cjs scripts, and I haven't written these tokens to a file. If you want, I can put them in the starter JSON format, generate tokens.css, and run the validator over src/.
Real run in an isolated folder with only this skill installed. Only the Skill and Read tools were enabled; the model answered from SKILL.md without opening the reference files, and no bundled script was run.
What it does
Helps the model set up design tokens and use them consistently: a three-layer structure (primitive values, semantic aliases, component tokens), CSS variables, spacing and typography scales, component state specs (hover, active, disabled), and Tailwind integration. It also includes a slide system with strategy, layout, typography, color and copywriting data tables for building HTML presentations.
How it works
SKILL.mdexplains the token architecture and points to reference files for each layer, component specs, states and Tailwind.generate-tokens.cjsturns a JSON token file into CSS variables or a Tailwind snippet;validate-tokens.cjsfinds hardcoded values in your code.- For slides,
search-slides.pysearches the bundled CSV tables for a deck strategy and layouts,generate-slide.pywrites an HTML deck, and the validators check token compliance.
Good for
Starting a design token system, documenting component states, converting design decisions into CSS variables, and producing brand-consistent pitch decks.
Node.js and Python scripts that read local files. Two write output: `generate-tokens.cjs` writes CSS to the `-o` path (overwrites that file without asking, creates missing folders) and `generate-slide.py` writes an HTML deck. Always pass `-o` to `generate-slide.py`: its default folder is worked out from the skill's install location (four levels above the script), so a user-level install lands outside your project. Point outputs at a throwaway or version-controlled folder. The scripts make no network requests (`fetch-background.py` only builds Pexels URLs); generated slides load Chart.js and fonts from public CDNs when opened in a browser. No account or key needed. SKILL.md mentions files and tools not in this package (`docs/brand-guidelines.md`, `assets/design-tokens.json`, `/slides:create`, the `brand` skill), so the slide workflow works best if your project already has them. Test file left out.