UIを追加・修正していくにあたって、UIの色・角丸・影のルールを一気に整備した。それまでは画面を作るたびにbg-blue-500やrounded-xlをその場で書いていて、見た目の統一感がじわじわ崩れていた。
「層」ではなく「責務」で分ける
最初に決めたのは、デザイントークンを4つの責務に分けること。
- テーマ契約: すべてのテーマが実装すべきCSS変数の一覧。名前と意味だけを決め、具体値は決めない
- テーマ実装:
themes/default.cssのような具体テーマファイルが、契約変数に実際の値を割り当てる - Tailwindアダプター:
tailwind.config.jsが契約変数をbg-surfaceのようなTailwindユーティリティに接続する - UIコンポーネント:
resources/views/components/ui/**がアダプター経由のユーティリティだけを使って組む
「層」という言葉を使わなかったのは意図的で、上下関係というより「誰が何に責任を持つか」を明確にしたかった。テーマ実装は自分のパレットを--theme-*という私的変数として持ってよいが、それを契約側に漏らしてはいけない。これによって、将来別のテーマ(例えばモノクロ系)を作るときに、既存テーマの色語彙に縛られずに済む。
ステータス系(status-danger / status-warningなど)は背景・文字色・枠線の三点セットで持つことにした。淡い警告ボックスと濃いdangerボタンを同じ1色で処理しようとすると破綻するので、最初から分けておく。ここは「意味カテゴリを裏切る変更をしない」という規律も明文化した。status-dangerを緑にするような変更は、技術的には可能でも規約違反という位置づけ。
アーキテクチャテストで機械強制する
ルールを文書に書くだけでは、時間が経つと誰かが(あるいは生成AIが)bg-blue-500を書いてしまう。そこでThemeTokenTestというアーキテクチャテストを作り、resources/views/components/ui/**配下で契約由来のユーティリティ以外の色・角丸・影クラスが使われていたら機械的に落とすようにした。
Tailwindの標準カラースケール名(slate、blue、emerald……)を列挙して、bg-やtext-などの接頭辞と組み合わさっているパターンを検出する形。禁止パターンを正規表現で決め打ちするのではなく、許可リスト方式にしたのは、新しいTailwindのユーティリティが増えても検出漏れが起きにくいようにするため。
このテストがあることで、「レビューで見た目のルール違反を人間が目視で拾う」という仕事がなくなった。CLAUDE.mdにルールを書いておけば生成AIも従うが、テストで強制した方が確実というのは、actorガードの規約のときと同じ学びだった。
Emeraldへの切り替えと specialized ボタン
ルール整備の直後、defaultテーマの配色をEmeraldベースに変更した。あわせて、用途別のbutton-add / button-edit / button-delete / button-cancelのようなspecializedボタンコンポーネントを追加した。汎用の<x-ui.button>に毎回variantや色クラスを指定するより、用途名のコンポーネントを用意した方が呼び出し側が迷わない。
その後は既存画面の移行を少しずつ進めた。ToDoCardのボタン、SetupWizardのボタン、といった単位でx-ui化のコミットを重ねている。CLAUDE.mdにも書いている通り、既存UIの一括置換はせず、新規追加時と大きく触るときに段階的に移行する方針を徹底した。