デザインシステムと聞くとFigmaのコンポーネントライブラリや複雑なStorybook構成を思い浮かべるかもしれませんが、CSSカスタムプロパティ(変数)を使えばコードだけでも一貫性のあるデザイントークン体系を構築できます。この記事では、実際のプロダクト開発で使えるカスタムプロパティの設計パターンとダークモード対応の実装方法を紹介します。
なぜカスタムプロパティがデザインシステムに向いているのか
CSS変数(`--variable-name: value`)は通常のCSSプロパティと違い、JavaScriptから動的に変更でき、かつカスケードとスコープを持ちます。Sass変数がビルド時に展開される静的な値であるのに対し、カスタムプロパティはランタイムに評価されるため、ダークモードの切り替えやユーザー設定の反映を再コンパイルなしに実現できます。また、コンポーネントの `style` 属性からカスタムプロパティを渡せるため、ReactやVueなどのフレームワークと組み合わせて動的なスタイリングを実装しやすい点も大きな利点です。
トークンの3層構造:グローバル・セマンティック・コンポーネント
設計上のベストプラクティスとして、デザイントークンを3層に分けることが推奨されています。第1層(グローバルトークン)は具体的な値の定義です。`--color-blue-500: #3b82f6` のように、意味を持たない「生の値」を定義します。第2層(セマンティックトークン)は用途別の別名です。`--color-primary: var(--color-blue-500)` のように、グローバルトークンを意味に結びつけます。第3層(コンポーネントトークン)は特定コンポーネント向けのオーバーライドです。`--button-bg: var(--color-primary)` のように、コンポーネント固有の変数がセマンティックトークンを参照します。この3層構造により、テーマ変更時にセマンティックトークンの値を差し替えるだけで、関連する全コンポーネントが一括更新されます。
ダークモードの実装パターン
カスタムプロパティを使ったダークモード実装の定石は、`:root` にライトモードの値を定義し、`@media (prefers-color-scheme: dark)` と `[data-theme="dark"]` の両方でダーク用の値を上書きする方法です。`@media` はOSの設定を自動で反映し、`[data-theme]` はJavaScriptからの手動切り替えを可能にします。重要なのは、コンポーネントのスタイルは必ずセマンティックトークンを参照し、ライト・ダークの生の値を直接書かないことです。`background: #ffffff` と書いてしまうと、ダークモード切り替えが効かなくなります。代わりに `background: var(--color-surface)` とセマンティックトークン経由で参照することで、テーマ変更が自動的に反映されます。
スペーシングとタイポグラフィのトークン設計
カラーと同様に、スペーシングとタイポグラフィもトークン化することで一貫性を保てます。スペーシングは `--space-1: 4px`、`--space-2: 8px`、`--space-4: 16px` のように4の倍数ベースで定義するのが一般的です。タイポグラフィは `--text-sm: 0.875rem`、`--text-base: 1rem`、`--text-lg: 1.125rem` のようにTypeScaleに従って定義します。フォントファミリーやフォントウェイトも変数化しておくと、ブランドフォントの変更時に1箇所だけ修正すれば済みます。Tailwind CSSはこの思想を `tailwind.config` に集約していますが、カスタムプロパティを使えばCSSフレームワークに依存せず同じメリットを得られます。
CSS Token GeneratorとColor Shade Generatorの活用
デザイントークンをゼロから手書きするのは労力がかかります。CSS Token Generatorを使えば、ベースカラーを入力するだけでライト・ダーク双方のセマンティックトークン定義をCSSコードとして出力できます。Color Shade Generatorでは1色から50〜950の明度バリエーションを生成でき、グローバルトークンのカラーパレットとして使えます。生成したコードは `:root` ブロックにそのまま貼り付けるだけで機能するため、既存プロジェクトへの導入コストも低く抑えられます。
まとめ
CSSカスタムプロパティによるデザインシステムの核心は「値を一箇所で定義し、セマンティックな名前で参照する」という原則にあります。3層トークン構造を採用し、コンポーネントが生の値ではなく意味のある変数を参照するよう設計することで、テーマ変更・ダークモード対応・ブランドアップデートを最小限のコスト変更で実現できます。小規模なプロジェクトでも、今日から始められるシンプルな習慣です。