Button からデザインシステムへ
この章で学ぶこと
ここまで、Button を題材に多くの技術を学んできました。最終章では、それらを「1 つのデザインシステム」としてまとめあげる視点を学びます。Button で身につけたことが、そのままデザインシステムの設計原則になっていたことを確認しましょう。
Button に凝縮されていたもの
はじめにで「Button にはデザインシステムの要素が詰まっている」と書きました。その答え合わせです。
| Button で学んだこと | デザインシステムの原則 |
|---|---|
デザイントークン(bg-primary) | 色・余白を意味で管理し、一貫させる |
| cva によるバリアント | コンポーネントの種類を宣言的に定義する |
| cn による安全な結合 | 利用者が安全にカスタマイズできる |
| Slot / asChild | コンポーネントを合成する |
| TypeScript の型 | 使い方を型で保証する |
| focus-visible / role | アクセシビリティを最初から組み込む |
これらは Button だけのものではありません。すべてのコンポーネントに共通する「良い設計の柱」です。
デザインシステムの 3 階層
デザインシステムは、おおまかに 3 つの階層で考えると整理できます。
③ パターン / 複合
フォーム、カード + ボタン、ダイアログ…
② プリミティブコンポーネント
Button, Input, Badge, Card, Alert…
① デザイントークン
色・余白・角丸・タイポ(@theme / OKLCH)
※ 下の層(①)が上の層(③)を支える
- ① トークン:すべての土台。デザイントークンの章で学んだ
--primaryなどの値 - ② プリミティブ:トークンを使った最小単位のコンポーネント。Button や Input
- ③ パターン:プリミティブを組み合わせた、より大きなまとまり
Button はちょうど②にあたります。①の上に乗り、③に組み込まれていきます。
一貫性はどこから生まれるのか
発展編で、Button・Badge・Alert を同じパターンで作りました。一貫性は、次の 2 つの「共通化」から生まれます。
1. 共通のトークン
すべてのコンポーネントが、同じ --primary や --destructive を参照します。
--primary(1 箇所で定義)
↓ すべてが共通で参照
Button / Badge / Alert …(みな bg-primary を参照)
--primary を 1 つ変えるだけで、すべてのコンポーネントの主役色が同時に変わります。これがトークンを中心に置く力です。
2. 共通のパターン
どのコンポーネントも cn + ...props を土台にし、必要に応じて cva と asChild を足す、という同じ作り方をします。作り方が揃っていると、新しいコンポーネントも迷わず作れ、読む人もすぐ理解できます。
ファイル構成と命名
shadcn/ui の標準的な構成は、デザインシステムの 3 階層をそのまま反映しています。
src/
├─ index.css # ① デザイントークン(@theme / :root / .dark)
├─ lib/
│ └─ utils.ts # cn 関数(全コンポーネント共通の道具)
└─ components/
└─ ui/ # ② プリミティブコンポーネント
├─ button.tsx
├─ badge.tsx
├─ input.tsx
├─ card.tsx
└─ alert.tsx
命名にも一貫したルールがあります。
- トークン:
名前/名前-foreground(背景とその上の文字) - バリアント:
variant(見た目)/size(大きさ) data-slot="button"など:スタイリングや識別のための目印
ルールが決まっていると、コードを見ただけで役割が分かります。
ドキュメント化
デザインシステムが育つと、「どんなコンポーネントがあり、どんなバリアントを持つか」を一覧できる場所が欲しくなります。代表的なのが Storybook です。
// button.stories.tsx(イメージ)
export const AllVariants = () => (
<div className="flex gap-2">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
</div>
)
こうしたカタログがあると、デザイナーや他の開発者と「同じものを見ながら」会話でき、車輪の再発明(似たボタンを各自が作ってしまう)を防げます。
チームで運用・拡張する
デザインシステムをチームで使い、育てていくときのポイントです。
- 新しい色は、まずトークンに足す:コンポーネントに直接
bg-green-500と書かず、--successトークンを定義してから使う(実践演習の章の演習がこれでした) - 新しい見た目は、cva の variant に足す:既存コンポーネントを拡張するときは、
variantsに 1 行足すだけ - アクセシビリティは最初から:
focus-visible・role・aria-*を後回しにしない - トークン変更の影響範囲を意識する:1 箇所の変更が全体に波及するのは利点だが、変えるときは影響範囲を確認する
デザインシステムは小さく始めて育てる
最初から完璧なデザインシステムを作る必要はありません。本書のように Button 1 つから始め、必要になったコンポーネントを同じパターンで足していけば、自然と一貫したシステムに育っていきます。
Button だけ → + Input / Badge → + Card / Alert → フォームやダイアログ…
(小さく始める) (少しずつ広げる)
次のステップ
本書を終えたら、次のような方向に学びを広げられます。
- shadcn/ui の複雑なコンポーネント:Dialog・DropdownMenu・Form など、Radix UI との連携が必要なもの
- ダークモードの実装:
next-themesなどで.darkクラスを切り替える - デザインツールとの連携:Figma のデザイントークンとコードのトークンを揃える
- フォーム周り:React Hook Form + Zod でのバリデーション
どれも、本書で学んだ「トークン・cva・cn・合成・型」の延長線上にあります。
本書のまとめ
おつかれさまでした。本書を通じて、次のことを学びました。
- デザイントークン:色・余白を意味で管理する(デザイントークンの章)
- cva:バリアントを宣言的に管理する(cva の章)
- cn:clsx + tailwind-merge でクラスを安全に結合する(cn の章)
- ref:React 19 で DOM にアクセスする(ref の章)
- Slot:asChild で要素を合成する(Slot の章)
- TypeScript:型安全なコンポーネント(TypeScript の章)
そして最後に、それらが 1 つのデザインシステムとしてつながることを見ました。
デザインシステムのベストプラクティスに沿って、再利用可能なコンポーネントを設計できる。
これが、はじめにで掲げたゴールでした。Button という小さな入り口から、その全体像までたどり着けたはずです。ぜひ、実際のプロジェクトで活用してください。