メインコンテンツまでスキップ

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 を土台にし、必要に応じて cvaasChild を足す、という同じ作り方をします。作り方が揃っていると、新しいコンポーネントも迷わず作れ、読む人もすぐ理解できます。

ファイル構成と命名

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-visiblerolearia-* を後回しにしない
  • トークン変更の影響範囲を意識する: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・合成・型」の延長線上にあります。

本書のまとめ

おつかれさまでした。本書を通じて、次のことを学びました。

  1. デザイントークン:色・余白を意味で管理する(デザイントークンの章
  2. cva:バリアントを宣言的に管理する(cva の章
  3. cn:clsx + tailwind-merge でクラスを安全に結合する(cn の章
  4. ref:React 19 で DOM にアクセスする(ref の章
  5. Slot:asChild で要素を合成する(Slot の章
  6. TypeScript:型安全なコンポーネント(TypeScript の章

そして最後に、それらが 1 つのデザインシステムとしてつながることを見ました。

デザインシステムのベストプラクティスに沿って、再利用可能なコンポーネントを設計できる。

これが、はじめにで掲げたゴールでした。Button という小さな入り口から、その全体像までたどり着けたはずです。ぜひ、実際のプロジェクトで活用してください。