実践的な Tailwind CSS: ゲートキーパー・アーキテクチャ
大規模な組織では、誰もがすべてのページにスタイルを適用できるわけではありません。コードをクリーンに保つために、特定の構造を採用しています。
Shopify Polaris のような成熟したデザインシステムでは、2つの異なる役割を使い分けています。
コンポーネント作成者 (ゲートキーパー): このチームはコアとなる UI を構築します。Tailwind クラスを記述し、アクセシビリティ、ダークモード、デザイントークンを管理します。
機能開発者 (アセンブラー): このチームは製品やダッシュボードを構築します。ビジネスロジックとレイアウトに集中し、色や余白(spacing)を直接選択することはありません。
組織は、Lint ルールやコードレビューによってこの境界を厳格に維持します。
推奨されること: • flex、grid、gap などのレイアウトユーティリティを使用すること。
避けるべきこと:
• feature ページ内に bg-indigo-600 や text-lg といった新しいビジュアルスタイルを直接追加すること。代わりに既存のコンポーネントを使用してください。
コードでの実装例は以下の通りです。
デザインシステムチームは、@/components/ui/button.tsx に再利用可能なボタンを作成します。ビジュアルスタイルをここに集約します。
import React from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
className?: string;
}
export function Button({
variant = 'primary',
className = '',
children,
...props
}: ButtonProps) {
const baseStyles = "px-4 py-2 rounded-lg font-medium text-sm transition-all focus:outline-none focus:ring-2 focus:ring-offset-2";
const variants = {
primary: "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-500",
secondary: "bg-slate-100 text-slate-700 hover:bg-slate-200 focus:ring-slate-500"
};
return (
<button className={`${baseStyles} ${variants[variant]} ${className}`} {...props}>
{children}
</button>
);
}
その後、機能開発者はこれらのブロックを組み合わせてページを構築します。
import { Button } from "@/components/ui/button";
export default function Dashboard() {
return (
<div>
<Button variant="primary" className="mt-4">
Save Changes
</Button>
</div>
);
}
この構造には、主に3つのメリットがあります。
- メンテナンスの容易化: ボタンを削除しても、不要なCSSが残ることはありません。
- アップデートの迅速化: ブランドカラーが indigo から violet に変わった場合、1つのファイルを変更するだけで、アプリ全体にその変更が反映されます。
- 一貫性: すべての開発者が同じデザイントークンを使用できます。
目標は、単にTailwindに関することだけではありません。それは、システムを構築する側と、それを利用する側の間に境界線を設けることにあります。
パート2では、アーキテクチャを壊すことなく、デザインの例外をどのように扱うかについて解説します。