コンポーネント契約とは
プロップの型は「何を受け取るか」を言います。契約は「何を絶対にしてはいけないか」を——AIエージェントが読める場所に——書き記します。
コンポーネント契約とは、ひとつのUIコンポーネントについての合意された仕様——入力(プロップ)、状態、使ってよいデザイントークン、アクセシビリティ要件、そして do / don't——を、人間にもAIコーディングエージェントにも読める形で書いたものです。
見た目だけでなく、どの実装も「越えてはいけない境界」を定義します。契約があれば、人でも別チームでもAIエージェントでも、デザインシステムを黙って破ることなくコンポーネントを再実装・拡張できます。ルールが「記憶」ではなく「明文」になっているからです。
上の一覧のうち、1つだけ説明を足します。デザイントークンとは、色・余白・文字といった見た目の値に名前を付けて、単一のソースにまとめたものです。#0a0a0a と直接書く代わりに --color-accent と書きます。契約は、そのうちどれをこのコンポーネントが使ってよいかを記録します。
プロップの型でもなく、Storybookのstoryでもない
あなたは既に ButtonProps のTypeScript型や、全バリアントを描くstoryを持っているかもしれません。どちらも有用ですが、どちらも契約ではありません。プロップの型は「何を受け取るか」を言い、storyは「どう見えうるか」を見せます。契約は、そのどちらも運ばないものを足す——越えてはいけない境界です。「primaryの背景はaccentトークン以外にしない」「このコンポーネントの状態はこの4つだけで、それ以外は無い」「フォーカスは常に見える」。
この発想はAPI契約から借りています。そこでは実装ではなく合意こそが、全員がそれに向けてコードを書く対象です。コンポーネント契約は、その規律をUIの表層に持ち込みます。
なぜ暗黙の契約はAIの下で壊れるか
再利用されるコンポーネントには、すでに契約があります。たいていそれは、デザイナーの頭・レビュアーの判断・ドキュメントの一段落に宿っています——記憶が運ぶ暗黙の契約です。それは、コンポーネントを読むのが「その場にいた人」である限りは機能します。
AIエージェントは、その場にいなかった。Claude Code や Cursor がコンポーネントを再生成するとき、どのトークンが許可され、どの状態が正当で、どのアクセシビリティ規則が譲れないのかを知りません。プロップの型は読める——だが「color.signal をボタン背景に使うな」は、型に現れない設計上の禁止です。だからエージェントは、無かったはずの5つ目の状態を発明し、近く見える色に手を伸ばします。その違反がコンポーネントを跨いで積み重なった状態が、近くで見たデザイントークン・ドリフトの姿です。
契約を実在させる
契約は、コンポーネントを作る全員が同じものを読んでこそ機能します——レビューの人間も、深夜2時のエージェントも。実在させるのは3つです。
- 各コンポーネントの 許可トークン・状態・アクセシビリティ規則・do/don't を明文化します——プロップだけでなきます。
- 人間とエージェントが同じ場所から読む 機械可読な単一ソース に置きます。
- 実装を契約に照らして検証します——不正なプロップだけでなく 境界を越えたトークンや状態 を指摘します。
Harnd では、各コンポーネント仕様が安定ID(cmp_)を持つ契約になっています。だから人間とエージェントは同じ合意に向けて実装し、境界を越えた瞬間は運ではなく品質ゲートが捉えます。
デザインシステムの単位は「契約」だ
デザインシステムの単位はコンポーネントだ、と思いがちです。違う——単位は合意です。再利用されるのはコードではありません。「そのコンポーネントが何であってよいか」という共有された理解です。その合意が書き留められていなければ、コンポーネントライブラリは共有された誤解のコレクションにすぎず、コピーのたびに少しずつ間違いが増えていきます。
型システムは、プログラムの契約を機械可読にした。コンポーネント契約は、デザイン上の決定に同じことをします——AIエージェントがあなたのUIの一級の実装者になった今、それが効いてきます。意図は、記憶ではなく契約として運ばれなければなりません。トークン・契約・品質ゲートを一つのソースに保つ層全体については、デザインハーネスとは何かを読んでほしい。
コンポーネントに、エージェントが実際に読める契約を持たせたいなら——早期アクセスに登録。