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.
できること
デザイントークンの整備と一貫した利用をモデルに支援させます。3 層構造(プリミティブ値、セマンティックなエイリアス、コンポーネントトークン)、CSS 変数、余白とタイポグラフィのスケール、コンポーネントの状態仕様(hover、active、disabled)、Tailwind との連携を扱います。ストーリー戦略、レイアウト、タイポグラフィ、配色、コピーの型などのデータ表を備えたスライドシステムも含まれ、HTML プレゼンテーションを作れます。
動き方
SKILL.mdがトークンの構造を説明し、各層、コンポーネント仕様、状態、Tailwind の参考ファイルを案内します。generate-tokens.cjsは JSON のトークンファイルを CSS 変数または Tailwind のスニペットに変換し、validate-tokens.cjsはコード内のハードコードされた値を検出します。- スライドでは、
search-slides.pyが同梱の CSV からデッキ構成とレイアウトを検索し、generate-slide.pyが HTML スライドを出力し、検証スクリプトがトークン準拠を確認します。
向いている場面
デザイントークンを一から整える、コンポーネントの状態を整理する、デザインの判断を CSS 変数に落とし込む、ブランドに沿ったピッチデッキを作る。
Node.js と Python のスクリプトを含み、ローカルファイルを読み取ります。ファイルを書き込むのは 2 つです。`generate-tokens.cjs` は `-o` で指定したパスに CSS を書き出し(同名ファイルは確認なしで上書きし、無いフォルダは作成します)、`generate-slide.py` は指定したパス、またはスクリプト既定の出力フォルダに HTML スライドを書き出します。使い捨てのフォルダか、バージョン管理されている場所を指定してください。スクリプト自体はネットワークリクエストを行いません。`fetch-background.py` は Pexels の画像 URL と検索リンクを組み立てるだけです。生成されたスライドをブラウザで開くと、Chart.js とフォントを公開 CDN から読み込みます。アカウントやキーは不要です。注意:SKILL.md には、このパッケージに含まれていないプロジェクトファイルやツール(`docs/brand-guidelines.md`、`assets/design-tokens.json`、`/slides:create` コマンド、同階層の `brand` Skill など)への言及があるため、スライド作成にはプロジェクト側でそれらを用意しておくのが望ましいです。このサイトの掲載版にはテストファイルを含めていません。`generate-slide.py` には必ず `-o` を指定してください。既定の出力先は Skill のインストール位置からスクリプトの 4 階層上として計算されるため、ユーザー単位でインストールしている場合はプロジェクトの外に出力されます。