「カードのborder-radiusは4pxにする? 8px?」
47分間のスレッドでした。エンジニア4人、デザイナー2人、バリアントが6つ並んだFigmaファイルが1つ。議論はiOSのコンベンション、Material Designのガイドライン、視覚的な整列、挙げ句の果てには黄金比にまで及びました。誰も間違ってはいなかった。それが問題でした。
こういう場面は初めてではありません。border-radiusだけでなく、グラデーションの方向、シャドウのぼかし値、disabled状態のopacity——どれも小さな判断です。どの議論もまっとうです。そして、それらが積み重なって、チームの開発速度を確実に蝕んでいました。
だから、正しい答えを探すのをやめました。問い自体を消したのです。
選択肢のコスト
デザインシステムのあらゆるビジュアルプロパティは、意思決定のポイントになります。border-radiusひとつとっても疑問は尽きません。ボタンとカードは同じ値にすべきか? 要素が入れ子になっている場合、内側の角丸は外側から差し引くのか? サイズによって角丸を変えるべきか?
どの問いも難しくありません。だからこそ厄介なのです。誰もが意見を持てるほど簡単で、どの意見が明らかに正しいとも言えないほど影響が小さい。典型的なバイクシェディング——些末なことほど議論が白熱する現象です。
あるクォーターでPRコメントを手作業で分類してみました。デザインシステム関連のレビュースレッドのうち、約30%がユーザビリティに一切影響しない装飾的な値についての議論でした。レイアウトの話ではなく、アクセシビリティでもなく、インタラクションパターンでもない。純粋に……見た目の話です。
そこで、ルールを書きました。
「制限」ではなく「排除」する理由
わかりやすい代替案は、プロパティは残しつつ選択肢を絞ることです。成熟したデザインシステムの多くがこのアプローチを取っています。border-radiusは3段階、シャドウはelevationスケール、グラデーションは厳選されたパレット。私たちも検討しました。
しかし、選択肢を制限しても議論は発生します。「このカードは radius-sm にする? radius-md?」は、答えの数が減っただけで同じ会話です。多いより少ないほうがいい。でも、少ないよりゼロのほうがもっといい。プロパティを制限すれば判断は減ります。プロパティを排除すれば判断そのものがなくなります。
リンティングによるアプローチも検討しました。許可されていない値を弾くESLintルールです。しかし「0、4、8pxだけ許可」というリンターを入れても、選択肢が3つ残ります。判断を楽にしたかったわけではありません。判断を不要にしたかったのです。
4つのルール
Beaketのコンポーネントライブラリには、4つのビジュアル制約があります。
- border-radius禁止。 すべてを直角に。ボタン、カード、インプット、ダイアログ、ツールチップ——すべて矩形です。
- グラデーション禁止。 フラットカラーのみ。すべてのサーフェスは単一のCSS変数で定義します。
- ぼかしシャドウ禁止。 blur radiusを持つ
box-shadowは使いません。オフセットシャドウのみです。 - 装飾的なopacity禁止。 disabled状態には透過ではなく色の変更を使います。
opacity: 0.5で済ませるのは禁止です。
これらはガイドラインではありません。コードレビューで強制し、コンポーネントガイドラインに明文化されたハードルールです。rounded-md を追加したPRはリジェクトされます。
なぜこの4つのプロパティなのか? ユーザビリティへの影響が最も小さいのに、最も議論を生んでいたプロパティだからです。スペーシングやタイポグラフィは可読性に影響します。色は意味を持ちます。しかし、border-radiusが4pxか8pxかの違いは? ぼかしシャドウが4pxか6pxかの違いは? その選択がプロダクトの使いやすさに計測可能な影響を与えたケースは、ただの1件も見つかりませんでした。
ルール導入後、デザインシステム関連のPRの進みが目に見えて速くなりました。レビュースレッドは短くなりました。デザイナーは、角の処理やシャドウの深さだけが違う複数バリアントを作らなくなりました。議論は減ったのではなく、消滅したのです。
代わりに何を使っているか
選択肢をなくすことは、ビジュアルヒエラルキーをなくすことではありません。深度、状態、インタラクションを伝える手段は依然として必要です。それぞれのプロパティをどう置き換えたかを説明します。
ぼかしの代わりにオフセットシャドウ
box-shadow: 0 4px 6px rgba(0,0,0,0.1) の代わりに、CSS変数で定義したハードオフセットシャドウを使っています。
--shadow-offset: 1px 1px 0px 0px var(--color-chrome);
--shadow-offset-hover: 2px 2px 0px 0px var(--color-chrome);
--shadow-offset-active: 0px 0px 0px 0px var(--color-chrome);
インタラクション状態は3つ。ぼかしはゼロ。これがシャドウシステムの全体像です。
デフォルト状態は1pxのオフセット——要素がサーフェスからわずかに浮いています。ホバーで2pxに——微かなリフト。アクティブで0pxにスナップ——要素が平面に押し込まれます。小さくメカニカルなアニメーションで、インタラクションを明確に伝えつつ、開発者が設定すべきものは何もありません。
実際には、すべてのインタラクティブなコンポーネントが同じ3つのトークンを適用します。以下はボタンコンポーネントのベース部分です(完全なCVA文字列にはdisabledやfocus状態も含まれますが、ここでは簡略化しています)。
const buttonVariants = cva(
[
"inline-flex items-center justify-center gap-2",
"font-medium cursor-pointer",
"shadow-offset hover:shadow-offset-hover active:shadow-offset-active",
"transition-shadow duration-100",
// ... disabled, focus-visible, and icon styles
].join(" "),
// ... variants
);
どのコンポーネントがどのシャドウレベルに「ふさわしい」かを判断する必要はありません。
CSS変数によるフラットカラー
私たちのカラーパレット——「Porcelain」と呼んでいます——は、graphite(#030509)から paper(#ffffff)まで14段階のニュートラルカラーで構成されています。冷たく、ブルーがかったトーンです。ドキュメントのテーマ説明にはこうあります。「工業的な精密さ。ブラックの合成ラッカーでコントロールパネルに刻印されたような。純白、冷たい青黒のインク、幽かなシャドウ、ティールのアクセント。」
すべてのカラー参照はセマンティックトークンを経由します。
--color-text-primary: var(--color-graphite);
--color-text-secondary: var(--color-steel);
--color-bg-primary: var(--color-paper);
--color-border-default: var(--color-chrome);
Tailwindデフォルトの bg-white や text-gray-500 は使いません。すべての色は名前付き変数であり、すべての変数は役割に紐づいています。gray-100 か gray-200 かで誰も悩まない——セマンティックトークンがすでに答えを出しているからです。
すべてを直角に
これは代替手段を用意する必要がありません。ただ……border-radiusを付けないだけです。
意外だったのは、直角とオフセットシャドウの相性の良さです。角丸のカードにオフセットシャドウを付けると違和感がある——シャドウのハードなエッジと柔らかい角が衝突します。しかし、直角のカードにオフセットシャドウを付けると、意図的に見える。ジオメトリに一貫性があるのです。制約同士が互いを補強し合う——これは事前に予想していなかった効果でした。
引き算によって、ビジュアル言語に一貫性が生まれます。すべてが直角であれば、どの要素もその形を正当化する必要がなくなります。
制約が複利で効くところ
このアプローチの最大のメリットは、横断的な関心事——すべてのコンポーネントに影響するパターンに現れます。
disabled状態は、コンポーネントライブラリ間で悪名高いほど不統一になりがちです。opacityを使うもの、背景色を変えるもの、両方やるもの。私たちのパターンはひとつだけで、すべてのコンポーネントに適用されます。
disabled:shadow-none disabled:cursor-not-allowed disabled:border-dashed disabled:border-chrome disabled:bg-frost disabled:text-steel
ボタン、インプット、セレクト、チェックボックス、スイッチ——すべて同一です。シャドウ除去(インタラクティブな手がかりの消失)、破線ボーダー、frostの背景、steelのテキスト。ひとつのdisabledコンポーネントを見れば、すべてを見たことになります。制約があることで、disabled状態を各コンポーネントの「ビジュアル個性」に合わせようという誘惑自体がなくなりました。コンポーネントにビジュアル個性がなければ、disabled状態は自然と収束します。
フォーカスリングも同様です。フォーカス可能なコンポーネントはすべて以下を使います。
focus-visible:outline-2 focus-visible:outline-signal-blue focus-visible:outline-offset-2
色はひとつ。太さはひとつ。オフセットはひとつ。キーボードでフォームを操作するユーザーは、どの要素でもまったく同じリングを目にします。コンポーネントがこれを上書きすることはなく、バリアントがこれを除去することもありません。統一されたdisabledパターンと組み合わせることで、ユーザーは「非活性」と「フォーカス中」が常にどう見えるかを正確に把握できます——どのコンポーネントを操作していても。
たったひとつの例外
ラジオボタンには rounded-full を使っています。
以上です。コンポーネントライブラリ全体で唯一の例外です。コンポーネントガイドラインには、この例外が破っているルールとともに記載されています。例外であっても、事故ではなく意思決定であるべきだからです。
四角いラジオボタンはユーザーを混乱させます。円形という形状は、インターフェースの慣習としてあまりにも深く根付いています。美学的な純粋さのためにユーザビリティを犠牲にするのは、割に合わないと判断しました。
こう思うかもしれません。ユーザビリティを理由にラジオボタンを例外にするなら、アバターやタグ、トグルスイッチはどうなのか? 検証しました。四角いアバターにオフセットシャドウを付けると、プロフィールカードのように見えます——意図的なデザインとして読める。タグやトグルは、ラジオボタンほど慣習に縛られていません。円形のアフォーダンスが決定的に強いのはラジオボタンだけで、他はその基準を超えませんでした。
「ブルータリズムのためのブルータリズム」が目的だったわけではありません。その複雑さに見合わないビジュアル上の判断を取り除くことが目的でした。角丸のラジオボタンは、その形状に見合う理由がある。角丸のカードにはない。
実際のコスト
このアプローチにデメリットがないと言ったら嘘になります。あります。
「未完成に見える。」 ユーザーテストで実際に出た反応です。直角とフラットカラーは、角丸やシャドウのあるインターフェースほど洗練された印象を与えません。理由を説明する場面は何度もありました。
一部のコンポーネントが硬く感じる。 特にツールチップやポップオーバーは、直角だと重たく見えます。ぼかしシャドウには理由がある——オフセットシャドウでは完全には再現できない「浮遊感」を生み出すのです。
サードパーティコンポーネントがパターンを崩す。 デートピッカー、リッチテキストエディタ、チャートライブラリ——どれもborder-radiusやぼかしシャドウが組み込まれています。上書きできるものは上書きし、端のほうの不整合は許容しています。これは解決済みの問題ではなく、現在進行形の課題です。
デザイナーの表現が制約される。 グラデーションオーバーレイや繊細な奥行き感を駆使したマイクロインタラクションに喜びを見出すタイプのデザイナーにとって、このシステムは窮屈に感じるでしょう。それが狙いではあるのですが、クリエイティブな満足度に対する実際のコストであることに変わりはありません。
賛否が分かれる。 シンプルさを愛するエンジニアもいれば、教条的だと感じるエンジニアもいます。この緊張関係を完全には解消できていません。ただ、私たちのプロダクトにおいては、一貫性のメリットが美学的なコストを上回ると判断しました。
まだわからないこと
このシステムを本当のスケールで検証できてはいません。数百のコンポーネント、データ密度の高いインターフェース、ネイティブに見せる必要があるサードパーティ統合。テーブル、チャート、ダッシュボードには、オフセットシャドウでは表現しきれない深度の手がかりが必要になるかもしれません。
私たちのシャドウ状態(デフォルト → ホバー → アクティブ)に対して、従来のelevationシステムと同じレベルの直感的な理解をユーザーが獲得できるかどうかも、正直なところ確信が持てていません。ユーザビリティの後退は観測されていませんが、この点に特化した正式なA/Bテストは実施していません。今のところうまくいっている。ただ、「今のところ」はまだ小さなサンプルです。
制約という名の意思決定
このシステムのすべての制約は、二度と繰り返さなくていいように一度だけ行った意思決定です。border-radius禁止は美学的な好みではなく、コンポーネントライブラリ全体に対するADR(Architecture Decision Record)です。
もしあなたのチームが、ユーザビリティに影響しないビジュアルプロパティの議論に時間を費やしているなら、その選択肢を丸ごと排除することを検討してみてください。思っているほど、困らないかもしれません。