Vanilla CSS Design Tokens Without Tailwind in 2026
2025年の後半にTailwind CSSを新しいプロジェクトで使うのをやめました。そしてそれ以来、振り返ることはありません。Tailwindが悪いからではなく、それは本当に優れているのです。しかし、Vanilla CSSがここまで良くなったため、当社が構築するようなプロジェクトではもはやトレードオフが合理的ではないのです。
設計トークンがこのシフトの中心にあります。設計上の決定を再利用可能なプラットフォーム非依存の変数として保存するという考え方は新しくありません。しかし、2026年のCSSカスタムプロパティの周辺のツーリングとブラウザサポートは、ビルドステップなし、依存関係なし、ユーティリティクラスの暗記なしで完全な設計トークンシステムを構築できるほど成熟しています。その方法を紹介します。
目次
- 設計トークンとは何か
- Vanilla CSSが私を魅了した理由
- トークンアーキテクチャの設定
- プリミティブトークン:生の値
- セマンティックトークン:魔法が起こるところ
- コンポーネントトークン:最終レイヤー
- フレームワークなしのダークモードとテーミング
- コンテナクエリを使ったレスポンシブトークン
- Figmaとの設計トークン同期
- パフォーマンス比較:Vanilla CSS対Tailwind
- 実世界のトークンファイル構造
- これを実用的にするツール
- よくある質問

設計トークンとは何か
設計トークンは、ビジュアル言語を定義する原子的な値です。色、間隔、タイポグラフィ、シャドウ、ボーダー半径、アニメーション期間 — 設計上の決定を表すものすべて。この用語は2014年にSalesforceの設計チームによって造られましたが、その概念は大きく進化しています。
多くの人が見落とす重要な洞察:設計トークンは単なる変数ではなく、設計とエンジニアリング間の契約です。デザイナーが「プライマリアクション色を使う」と言ったら、それはヘックス値ではなくトークンにマップします。「中程度の間隔」と言ったら、それもトークンです。
W3C設計トークンコミュニティグループは設計トークン形式モジュール仕様を公開しました。まだ進化中ですが、中核的な考え方は固まっています。トークンはレイヤーに存在します:
- プリミティブトークン(
#1a73e8や16pxのような生の値) - セマンティックトークン(
color-action-primaryのような目的駆動型エイリアス) - コンポーネントトークン(
button-backgroundのようなUI要素に限定)
この3層のアーキテクチャが大規模システムを管理可能にします。CSSカスタムプロパティは完璧にそれにマップします。
Vanilla CSSが私を魅了した理由
私の経験について正直に言います。私は何年も前からTailwindを喜んで使っていました。本番サイトを構築し、クライアントに推奨しました。しかし、いくつかのことが変わりました。
CSSがキャッチアップしました。 ネスティング、:has()、コンテナクエリ、@layer、@scope、color-mix()、oklch()、相対色構文 — これらはもはや試験的な機能ではありません。2026年の主流ブラウザでは基線です。ユーティリティフレームワークが埋めていたギャップは、劇的に縮小しました。
バンドルサイズがかつてないほど重要です。 コアウェブバイタルが検索ランキングに直接影響を与える中、すべてのキロバイトが重要です。適切に構成されたVanilla CSSトークンシステムはほぼ何も重みません。Tailwindのコンパイラは賢いですが、まだ使用するすべてのユーティリティのCSSを配信します。
メンテナンスコストは現実です。 30以上のユーティリティクラスが連結されているコンポーネントを持つTailwindプロジェクトを引き継ぎました。className="flex items-center justify-between px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 border border-gray-200 dark:border-gray-700"を読むのは楽しくありません。設計トークンと薄いCSSレイヤーでは、意図がより明確です。
設計エンジニアリングハンドオフが改善されました。 CSSのトークンがFigmaのトークンと1:1で一致すると、会話は正確になります。もはや「このFigma変数はどのTailwindクラスにマップするのか」という会話はありません。
これは宗教戦争ではありません。Tailwindがチームのために機能するのであれば、使い続けてください。しかし新しい何かを始めていて、最大の制御と最小の抽象化を望むのであれば、Vanilla CSSトークンは真剣に検討する価値があります。
トークンアーキテクチャの設定
ゼロから構築しましょう。Social Animalで複数のヘッドレスCMSプロジェクトで洗練されたファイル構造を使用します。ニーズに合わせて調整してください。
コア原則:トークンは特異性レイヤーを通じて下向きに流れる。
tokens/
├── primitives.css /* Raw values */
├── semantic.css /* Purpose-driven aliases */
├── components/
│ ├── button.css
│ ├── card.css
│ └── input.css
├── themes/
│ ├── light.css
│ └── dark.css
└── index.css /* Import orchestration */
index.cssは@layerを使用して明確なカスケードを確立します:
@layer primitives, semantic, themes, components;
@import './primitives.css' layer(primitives);
@import './semantic.css' layer(semantic);
@import './themes/light.css' layer(themes);
@import './components/button.css' layer(components);
@import './components/card.css' layer(components);
@import './components/input.css' layer(components);
これは重要です。@layerは特異性を戦わずにカスケードを明示的に制御します。プリミティブはセマンティックトークンでオーバーライドされ、テーマでオーバーライドされ、コンポーネントでオーバーライドされます。クリーンなヒエラルキーです。

プリミティブトークン:生の値
プリミティブはパレットです。コンポーネントで直接使用することを意図していません — 何を塗るかを決める前に、棚の絵の具と考えてください。
/* primitives.css */
:root {
/* Colors using OKLCH for perceptually uniform palettes */
--color-blue-50: oklch(0.97 0.01 250);
--color-blue-100: oklch(0.93 0.03 250);
--color-blue-200: oklch(0.87 0.06 250);
--color-blue-300: oklch(0.78 0.10 250);
--color-blue-400: oklch(0.68 0.15 250);
--color-blue-500: oklch(0.58 0.19 250);
--color-blue-600: oklch(0.50 0.19 250);
--color-blue-700: oklch(0.42 0.17 250);
--color-blue-800: oklch(0.35 0.14 250);
--color-blue-900: oklch(0.27 0.10 250);
/* Spacing scale (modular, based on 4px grid) */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
/* Typography scale */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-md: 1.125rem;
--font-size-lg: 1.25rem;
--font-size-xl: 1.5rem;
--font-size-2xl: 1.875rem;
--font-size-3xl: 2.25rem;
--font-size-4xl: 3rem;
/* Font weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Border radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px oklch(0 0 0 / 0.05);
--shadow-md: 0 4px 6px oklch(0 0 0 / 0.07);
--shadow-lg: 0 10px 15px oklch(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px oklch(0 0 0 / 0.1);
/* Durations */
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--duration-slower: 500ms;
}
OKLCHを使用しているのは、本当により良い色空間だからです。HSLとは異なり、OKLCHの明度は知覚的に均一です — 明度が0.5は、色合いに関係なく本当に中くらいの灰色に見えます。色スケールはスペクトラム全体で一貫して見えます。主要なブラウザはすべてこれをサポートしています。
セマンティックトークン:魔法が起こるところ
セマンティックトークンはプリミティブを参照し、意味を与えます。これはテーマを可能にするレイヤーであり、CSSを自己記述的にします。
/* semantic.css */
:root {
/* Surface colors */
--color-surface-primary: var(--color-white, #fff);
--color-surface-secondary: var(--color-gray-50);
--color-surface-tertiary: var(--color-gray-100);
--color-surface-inverse: var(--color-gray-900);
/* Text colors */
--color-text-primary: var(--color-gray-900);
--color-text-secondary: var(--color-gray-600);
--color-text-tertiary: var(--color-gray-400);
--color-text-inverse: var(--color-white, #fff);
--color-text-link: var(--color-blue-600);
--color-text-link-hover: var(--color-blue-700);
/* Action colors */
--color-action-primary: var(--color-blue-600);
--color-action-primary-hover: var(--color-blue-700);
--color-action-primary-active: var(--color-blue-800);
/* Feedback colors */
--color-feedback-success: var(--color-green-600);
--color-feedback-warning: var(--color-amber-500);
--color-feedback-error: var(--color-red-600);
--color-feedback-info: var(--color-blue-500);
/* Border colors */
--color-border-primary: var(--color-gray-200);
--color-border-secondary: var(--color-gray-100);
--color-border-focus: var(--color-blue-500);
/* Semantic spacing */
--space-inline-xs: var(--space-1);
--space-inline-sm: var(--space-2);
--space-inline-md: var(--space-4);
--space-inline-lg: var(--space-6);
--space-stack-xs: var(--space-1);
--space-stack-sm: var(--space-2);
--space-stack-md: var(--space-4);
--space-stack-lg: var(--space-8);
--space-stack-xl: var(--space-12);
}
ここには何もハードコーディングされた値がありません。すべてはプリミティブを参照します。明日ブランド変更したい場合は、プリミティブを変更します。「プライマリアクション」の感覚を微調整したい場合は、セマンティックトークンを変更します。コンポーネントコードは知る必要がありません。
コンポーネントトークン:最終レイヤー
コンポーネントトークンは設計上の決定を特定のUI要素にスコープします。小さいプロジェクトではオプションですが、設計システムを持つ何かに必須です。
/* components/button.css */
.button {
--_bg: var(--color-action-primary);
--_bg-hover: var(--color-action-primary-hover);
--_bg-active: var(--color-action-primary-active);
--_text: var(--color-text-inverse);
--_radius: var(--radius-md);
--_padding-block: var(--space-2);
--_padding-inline: var(--space-4);
--_font-size: var(--font-size-sm);
--_font-weight: var(--font-weight-semibold);
--_shadow: var(--shadow-sm);
--_transition: var(--duration-normal);
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--_padding-block) var(--_padding-inline);
background: var(--_bg);
color: var(--_text);
font-size: var(--_font-size);
font-weight: var(--_font-weight);
border-radius: var(--_radius);
box-shadow: var(--_shadow);
transition: background var(--_transition), box-shadow var(--_transition);
border: none;
cursor: pointer;
&:hover {
background: var(--_bg-hover);
box-shadow: var(--shadow-md);
}
&:active {
background: var(--_bg-active);
}
&.--secondary {
--_bg: transparent;
--_text: var(--color-action-primary);
--_shadow: none;
border: 1px solid var(--color-border-primary);
&:hover {
--_bg: var(--color-surface-secondary);
}
}
&.--ghost {
--_bg: transparent;
--_text: var(--color-text-primary);
--_shadow: none;
&:hover {
--_bg: var(--color-surface-secondary);
}
}
}
--_プレフィックス規則(アンダースコア付き)は「プライベート」コンポーネントトークンを通知します。ブラウザでは強制されませんが、他の開発者にとって明確なシグナルです:これらのトークンはこのコンポーネントの内部です。
また — CSSネスティングがここでどのように機能するかを見てください。プリプロセッサは必要ありません。これは2026年のネイティブCSSです。
フレームワークなしのダークモードとテーミング
ここでトークンアーキテクチャが本当に報酬を与えます。ダークモードはセマンティックトークンを再割り当てする問題になります。
/* themes/dark.css */
@media (prefers-color-scheme: dark) {
:root {
--color-surface-primary: var(--color-gray-900);
--color-surface-secondary: var(--color-gray-800);
--color-surface-tertiary: var(--color-gray-700);
--color-surface-inverse: var(--color-white, #fff);
--color-text-primary: var(--color-gray-50);
--color-text-secondary: var(--color-gray-300);
--color-text-tertiary: var(--color-gray-500);
--color-text-inverse: var(--color-gray-900);
--color-text-link: var(--color-blue-400);
--color-text-link-hover: var(--color-blue-300);
--color-border-primary: var(--color-gray-700);
--color-border-secondary: var(--color-gray-800);
--shadow-sm: 0 1px 2px oklch(0 0 0 / 0.2);
--shadow-md: 0 4px 6px oklch(0 0 0 / 0.3);
--shadow-lg: 0 10px 15px oklch(0 0 0 / 0.4);
}
}
/* Manual toggle support */
[data-theme="dark"] {
--color-surface-primary: var(--color-gray-900);
/* ... same overrides ... */
}
コンポーネントはまったく変わりません。ダークモードクラスなし。条件付きロジックなし。トークンがすべてを処理します。
ホワイトラベルクライアント向けのブランドテーマが必要ですか?同じパターン:
[data-theme="client-acme"] {
--color-blue-500: oklch(0.55 0.20 280); /* Their purple instead of blue */
--color-action-primary: var(--color-blue-500);
--radius-md: 1rem; /* They like rounder corners */
}
このアプローチは、マルチテナントテーミングが共通の要件であるNext.js開発プロジェクトで広く使用されます。
コンテナクエリを使ったレスポンシブトークン
これは、Tailwindで本当にうまくできないもの(少なくともエレガントにはできない)です。コンテナクエリでは、ビューポートだけでなくコンテナサイズに基づいてトークン値を変更できます。
.card-grid {
container-type: inline-size;
container-name: card-grid;
}
@container card-grid (max-width: 500px) {
.card {
--_padding: var(--space-3);
--_title-size: var(--font-size-base);
--_gap: var(--space-2);
}
}
@container card-grid (min-width: 501px) {
.card {
--_padding: var(--space-6);
--_title-size: var(--font-size-lg);
--_gap: var(--space-4);
}
}
ビューポートではなく独自のコンテキストに応答するコンポーネント。これは、コンポーネントライブラリを構築する場合、特にAstroサイトで強力です。コンポーネントが全く異なるレイアウト全体で再利用されます。
Figmaとの設計トークン同期
Figma Variables(2023年にリリースされ、その後大幅に拡張)は当社の3層トークンモデルに直接整合します。ワークフローは次のとおりです:
- Figmaでトークンを定義(Variables パネルを使用してプリミティブ → セマンティック → コンポーネント)
- トークンをエクスポート(Tokens Studioプラグインまたはfigma REST APIを使用)
- CSSに変換(Style DictionaryまたはCobalt UIツールを使用)
- リポジトリにコミット(トークン/ディレクトリとして)
CSS出力のStyle Dictionary設定は次のようになります:
{
"source": ["tokens/**/*.json"],
"platforms": {
"css": {
"transformGroup": "css",
"buildPath": "src/styles/tokens/",
"files": [
{
"destination": "primitives.css",
"format": "css/variables",
"filter": { "filePath": "tokens/primitives" }
},
{
"destination": "semantic.css",
"format": "css/variables",
"filter": { "filePath": "tokens/semantic" }
}
]
}
}
}
結果は、設計の変更がFigmaからCSSトークンに自動的に流れるCIパイプラインです。手動翻訳なし、ドリフトなし。
パフォーマンス比較:Vanilla CSS対Tailwind
I ran benchmarks on a real marketing site we rebuilt -- same design, two implementations. Here are the numbers:
| メトリック | Tailwind v4 | Vanilla CSS tokens | 差分 |
|---|---|---|---|
| 合計CSSサイズ(gzip) | 14.2 KB | 6.8 KB | -52% |
| First Contentful Paint | 1.2s | 1.0s | -17% |
| Largest Contentful Paint | 2.1s | 1.8s | -14% |
| CSSパース時間 | 3.2ms | 1.4ms | -56% |
| HTMLサイズ(gzip) | 28.4 KB | 22.1 KB | -22% |
| ビルド時間 | 1.8s | 0.4s* | -78% |
*Lightning CSSを介したバンドリングのみのVanilla CSS。
HTMLサイズの差は注目に値します — Tailwindのユーティリティクラスはマークアップに重大な重みを追加します。200以上の要素を持つページでは、これらのクラス文字列は合計します。
ただし、2025年初期にリリースされたTailwind v4(Oxideエンジン)では大幅な改善が行われました。ギャップが縮小しました。しかし、ビルド依存なしのVanilla CSSは、生パフォーマンスで本当に勝つのは難しいです。
実世界のトークンファイル構造
ここは最近のプロジェクトからの実際の構造です — Next.jsとSanityで構築されたヘッドレスコマースサイト:
src/styles/
├── tokens/
│ ├── primitives/
│ │ ├── colors.css
│ │ ├── spacing.css
│ │ ├── typography.css
│ │ ├── shadows.css
│ │ ├── borders.css
│ │ └── motion.css
│ ├── semantic/
│ │ ├── colors.css
│ │ ├── spacing.css
│ │ └── typography.css
│ ├── themes/
│ │ ├── light.css
│ │ ├── dark.css
│ │ └── high-contrast.css
│ └── index.css
├── components/
│ ├── button.css
│ ├── card.css
│ ├── input.css
│ ├── modal.css
│ ├── navigation.css
│ └── typography.css
├── layouts/
│ ├── grid.css
│ └── container.css
├── utilities/
│ └── helpers.css /* A few utility classes we actually need */
├── reset.css
└── main.css
utilities/helpers.cssファイルは興味深いです — 本当に再利用可能なパターンのために一握りのユーティリティクラスをまだ書いています:
/* utilities/helpers.css */
.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
.flow > * + * {
margin-block-start: var(--flow-space, var(--space-stack-md));
}
.cluster {
display: flex;
flex-wrap: wrap;
gap: var(--cluster-gap, var(--space-inline-md));
align-items: center;
}
ユーティリティもトークンを参照します。すべてが接続されたままです。
これを実用的にするツール
多くは必要ありませんが、いくつかのツールはDXを大幅に改善します:
| ツール | 目的 | メモ |
|---|---|---|
| Lightning CSS | バンドリング、ミニフィケーション、ベンダープレフィックス | PostCSSのほとんどのユースケースを置き換えます。信じられないほど高速。 |
| Style Dictionary | トークン形式の変換 | Figma → CSS、iOS、Androidを単一ソースから |
| Cobalt UI | W3C DTCGトークンツール | Style Dictionaryの新しい代替案、仕様準拠 |
| Tokens Studio | Figmaプラグインでトークンを管理 | 最高クラスのFigma統合 |
| CSS Utility Kit (VS Code) | カスタムプロパティのオートコンプリート | ホバー時にトークン値を表示 |
| Open Props | 事前作成されたCSSカスタムプロパティライブラリ | ゼロから構築したくない場合の優れた出発点 |
Lightning CSSは特別なコールアウトに値します。Rustで記述され、単一のパスで@importインライン化、古いブラウザのネスティングコンパイル、ミニフィケーションを処理します。これはほとんどのヘッドレスCMSビルドで今使用しているものです。
lightningcss --bundle --minify src/styles/main.css -o dist/styles.css
それだけです。PostCSS設定なし。プラグインチェーンなし。1つのコマンド。
よくある質問
ビルドステップなしで設計トークンを使用できますか?
絶対に。現代的なブラウザをターゲットにしている場合(2026年では、すべきです)、ネイティブCSSカスタムプロパティはコンパイルなしで機能します。失うのは@importバンドリングだけです —ブラウザは複数のCSSファイルインポートをうまく処理します。本番パフォーマンスのためにバンドルしたいところです。Lightning CSSまたは単純なcatコマンドでもそれを処理できます。
Vanilla CSS設計トークンはTailwind v4とどう比較しますか?
Tailwind v4は実はCSSカスタムプロパティに内部的に移行しました。これはアプローチを検証します。違いは開発者体験にあります。TailwindはHTMLで適用するユーティリティクラスを提供しますが、Vanilla トークンは独自のCSSで使用する設計変数を提供します。Vanilla トークンはより小さいCSSとHTML出力を生成し、テーミングに対してより多くの柔軟性を提供し、クラス命名システムを学ぶ必要がありません。Tailwindはより大きなチームの方が高速なプロトタイピングと強力な一貫性の強制を提供します。
設計トークンのTypeScript型安全性はどうですか?
CSS ModulesまたはCSS-in-JSを使用している場合、トークンファイルからTypeScript型を生成できます。Style Dictionary 4とCobalt UIはどちらもTypeScript出力をサポートしています。純粋CSSの場合、CSS変数オートコンプリートなどのVS Code拡張機能は、オートコンプリートと検証を使用してエディタレベルの安全性を提供します。
Open Propsの代わりに独自のトークンを構築すべきですか?
Open Propsは、特にプロトタイプや小規模プロジェクトの場合、優れた出発点です。すぐに使用可能なCSSカスタムプロパティのセットを提供します。本番設計システムでは、ブランドに一致するプリミティブを定義したいでしょう。Open Propsを参照として使用し、イージング関数やシャドウスケールのようなものへのアプローチをチェリーピックできます。
複数のアプリを含むモノレポで設計トークンを処理するにはどうしますか?
CSSファイルをエクスポートする共有tokensパッケージを作成します。各アプリはトークンパッケージをインポートし、独自のテーマオーバーライドを追加できます。これはpnpmまたはnpmのワークスペースで美しく機能します。プリミティブトークンを共有しますが、ブランドごとにユニークなセマンティックマッピングを持つマルチブランドeコマースクライアントについてもこれをしました。
Vanilla CSSはTailwindより開発が遅いですか?
最初はそうです — Tailwindが無料で提供するインフラストラクチャを設定しています。ただし、最初の週か2週間後、速度は同等またはそれ以上です。HTMLとユーティリティクラスリファレンスの間で文脈を切り替えていません。英語のようなCSSを書いています。何かを変更する必要がある場合、すべてのインスタンスを検索する代わりに1つの場所で変更します。
チームをTailwindをドロップするように説得するにはどうしますか?
Tailwindをドロップするとしてフレーミングしないでください — 設計トークンを採用するとしてフレーミングしてください。Tailwindの隣にトークンレイヤーを導入することから始めてください:カスタムプロパティを定義し、tailwind.configテーマで参照します。時間の経過とともに、トークンシステムが大部分の仕事を処理することに気づき、Tailwindはオプションになるかもしれません。チームがその結論に有機的に到達できます。
Tailwindに依存するShadcn/UIのようなコンポーネントライブラリはどうですか?
Shadcn/UIは素晴らしいですが、Tailwind依存は基本的なアーキテクチャではなく実装の詳細です。コンポーネントパターンとアクセシビリティロジックが重要です。Tailwindクラスの代わりにVanilla CSSトークンを使用するいくつかのコミュニティフォークが浮上しました。Shadcn コンポーネントをトークンシステムに徐々に移行することもできます — 基になるRadix UI プリミティブは、スタイルする方法を気にしません。
このアプローチはAstroまたはNext.jsで使用できますか?
絶対に。両方のフレームワークは、ゼロ設定でVanilla CSSを本来サポートしています。Astroのスコープされたスタイルはカスタムプロパティで完璧に機能します。:rootで定義されたトークンは、スコープされたコンポーネントスタイルに自然にカスケードするからです。Next.jsは、グローバルトークンファイルの隣のCSS Modulesをサポートしています。Next.jsプロジェクトとAstroビルドでこの正確なセットアップを定期的に使用しています。新しいプロジェクトについて探索している場合は、お問い合わせください — 学んだことをお喜びして共有します。