1 分

デザイントークンとルールで React アプリの UI を一貫させる

デザイントークンとコンポーネント/フォームルールを使ったプロンプトで、AI生成の React 画面がスペーシング、タイポ、フォーム挙動まで一致するようにする方法を解説します。

デザイントークンとルールで React アプリの UI を一貫させる

なぜ生成された React 画面はしばしば一貫して見えないのか

UI の不一致は、クリックして回ると違和感につながる小さな差異として現れます。あるページは余裕のあるパディングで、別のページは窮屈に感じる。見出しがサイズを行き来し、ボタンの形や色が変わり、同じ入力が画面ごとに挙動を変える。

多くの場合、その原因は基本的な要素が揺らいでいることです:

  • スペーシング:パディング、ギャップ、セクションのリズムがばらばら
  • タイポグラフィ:フォントサイズ、ウェイト、行の高さが安定していない
  • コンポーネント:ボタン、カード、ボーダー、シャドウが微妙に違う
  • フォーム:検証タイミング、エラーの配置、無効状態の扱いが異なる

別々のプロンプトから画面を生成するとこれはよく起きます。各プロンプトは事実上のゼロからの開始なので、モデルは不足する決定を推測で埋めます。「モダンなスタイルを使って」と言っても、8px と 12px のギャップ、14px と 16px の本文、いつエラーを表示するか、プライマリボタンの見た目など、何百もの小さな判断が残ります。

2〜3ページを手作業で整えることはできますが、スケールしません。ワンオフの CSS 修正を追いかけ、ファイル間でスタイルをコピーし、フォームを何度も直すことになります。ルールはプロジェクトの外にあって頭の中にしかありません。

今日ログイン画面を生成して、明日プロフィール画面を生成したと想像してください。1つは submit のみでエラーを出し、もう1つは blur でエラーを出すと、ユーザーは気づきます。画面間でプライマリボタンの高さが変わると、アプリは継ぎ接ぎに見えます。

すべての画面が同じ共有システムに従うと、一貫性がデフォルトになります:デザイントークン(スペーシング、タイポ、カラー)と、小さなコンポーネントおよびフォームのルールセットです。

平易な言葉でのデザイントークン

デザイントークンは UI 全体で再利用する単純な名前付きの値です。毎回「快適なパディング」と頼む代わりに、space-4 のようなトークンを使います。「やや丸める」と言う代わりに radius-md を使います。名前は安定して、後で対応先を変えても名前は変わりません。

トークンは、すべての画面で共有したい決定の集合です。味付けや推測を取り除くことで、生成や新しいページの作成時に生じるドリフトを防ぎます。

典型的なトークンはスペーシング、タイポグラフィ、色、形、そして少量のエレベーションをカバーします。実利としては:ヘッダーは常に同じサイズ、カードは常に同じパディング、プライマリボタンは同じ色と角丸を保つ、ということです。

何をトークン化し、何を柔軟にするか

プロダクト全体の雰囲気に影響するものをトークン化してください:スペーシングスケール、フォントサイズと行の高さ、コアカラー(テキスト、背景、プライマリ、デンジャー、ボーダー)、そして少数のボーダー半径。

コピーの長さ、どのアイコンを使うか、セクションにカードが2つ必要か3つ必要かなど、コンテンツに依存する選択は柔軟にしておきます。

画面を生成する際(Koder.ai のようなツールを使う場合も含む)、事前に小さなトークンセットを与えると推測の余地が減り、出力が明らかに一貫します。

小さくても完結したトークンセットを選ぶ

トークンセットは、許可する値の短いメニューにすぎません。小さいほどランダムな選択の余地が減るが、画面を台無しにする基本はカバーしている必要があります。

まずスペーシングから始めます。1つのスケールを選び、パディング、ギャップ、レイアウトにそこを使います。4、8、12、16、24、32 のようなセットは多くの UI をカバーします。デザインで 10px や 18px が要求されたら、新しい値を導入する代わりに近いトークンに丸めてください。

次にタイポグラフィのデフォルトを定義して、見出しや本文のぶれを止めます。大規模なタイプシステムは必要ありませんが、明確で再現可能な段階が必要です。

無駄に膨らまないコンパクトなセットの例:

  • Spacing: 4, 8, 12, 16, 24, 32
  • Type: 本文1サイズ、見出し2サイズ、本文用行高1つ、見出し用行高1つ
  • Colors: text-primary, text-muted, bg, surface, border, accent
  • Radius: 2つ(例:4 と 12)
  • Elevation: none, small, medium(最大3レベル)

アクセシビリティもシステムに含めます。フォーカスアウトラインのスタイル(色、太さ、オフセット)を定義してキーボードユーザーに一貫したフォーカス状態を提供します。モバイル向けの最小タップターゲット(44x44 など)も設定します。テキスト色は信頼できる少数に限定してコントラストを予測可能に保ちます。

ボタンが狭く見える原因は、ある画面でパディングが 10 を使い、別の画面で 12 を使ったことにあることが多いです。トークンを使えば「ボタンは paddingY=8, paddingX=16, radius=12, フォーカストークン, 最小高さ44 を使う」と決められます。数値を固定すれば、ジェネレーターは即興をやめます。

すべての画面を揃えるコンポーネントルール

トークンは数値を与えます。コンポーネントルールは習慣を定めます。

コアコンポーネントの小さなセットを選び、画面の構築ブロックはそれだけにします。退屈で再利用可能なものにしてください:Button、Input、Select、Checkbox、Card。TextArea や Modal は追加できますが、ラベル、間隔、状態は同じシステムに従わせます。

次にバリアントを制限し、使用を定義します。例えば:Button は primary、secondary、danger。Primary は画面上の主要アクション(通常は1つ)に使います。Secondary はキャンセルや優先度の低いアクションに。Danger は削除のような破壊的アクションのみにします。正当化できないバリアントはデフォルトで secondary にします。

スペーシングルールは微妙なズレを防ぎます。コンポーネント内のデフォルトを定義してください:ボタンのパディング、入力の高さ、ラベルとフィールドの間隔、積み重なったフィールド間の標準ギャップ。レイアウトルールも少し追加します:カードは内部パディングが固定、ヘッダ/ボディの間隔が一貫、モーダルは幅のステップとフッターの整列が同じ。

最後に状態を非交渉にします。ここが UI がランダムに見え始める箇所です:

  • Loading: コントロールを無効化し、スピナーを表示し、幅を安定させる
  • Disabled: コントラストを下げるがホバー/フォーカススタイルを完全に消さない
  • Empty states: 短いメッセージと明確なアクション
  • Validation: エラーはフィールド下に出し、大きなレイアウトシフトを起こさない

「Create project」のようなフォーム中心の画面を生成するとき、これらのルールが混在するボタンサイズやラベル位置のズレ、画面にしかない“特別な”カードの出現を防ぎます。

通常ズレが発生するフォーム動作ルール

プロンプトをプロダクトにする
画面とルールを平易な言葉で記述して、Web・サーバ・モバイルアプリを作ります。

視覚が安定していても、多くの「違和感」の原因はフォーム挙動にあります。各画面でラベル、エラー、フォーカスの扱いが異なると、ユーザーは違和感を覚えます。

フォームパターンを一つ選び、どこでもそれを使ってください:ラベル、オプション/必須マーカー、ヘルパーテキスト、その後にエラーテキスト。文言も揃えてください(例えばラベルは文の先頭のみ大文字、ヘルパーは短く、エラーメッセージは動詞で始める等)。

ドリフトを防ぐルールの例:

  • ラベルは常にフィールド上(プレースホルダーではない)、必須マーカーはラベル上に表示
  • ヘルパーテキストは常にフィールドの下、同じサイズと色、エラーには使わない
  • 検証タイミング:blur または submit 後にエラーを表示、change でクリア
  • エラー配置:フィールド直下に1行、ヘルパーテキストと揃えて表示
  • フォーカス挙動:Tab 順序は視覚レイアウトに従う。Enter は最後のフィールドか、プライマリボタンにフォーカスがあるときのみ送信

サイズとレイアウトを固定して、画面ごとに「呼吸」が違わないようにします。入力の高さ1つ、ボタンの高さ1つ、デフォルトのフィールド幅1つを定義してください。デスクトップではフィールドを一貫したグリッドに揃えてラベルは上に、モバイルではフィールドを全幅にして二列フォームは本当に必要な場合だけにします。

簡単な例:「Create project」画面に Name、Region、Description があるとします。Region が select であっても他のフィールドと同じ扱い:同じ高さ、同じラベル位置、同じエラー行。ユーザーが Name を空で送信すると Name にフォーカスが移り、その下にエラーが出てレイアウトは安定したままです。

Koder.ai で画面を生成するなら、これらのフォームルールをプロンプトに一度入れて機能全体で再利用すれば、新しいフォームも繰り返し修正する必要がなくなります。

プロンプトにトークンとルールを表現する方法

プロンプトを小さな UI 契約のように扱ってください。短く、具体的で再利用可能にすれば、各画面が同じスペーシング、タイポ、コンポーネント、挙動にスナップします。

実用的なパターンは、リクエストの先頭にコンパクトな UI 仕様を貼り、その下に画面を平易に記述する方法です。

コンパクトなプロンプトテンプレート

UI SPEC (apply to every screen)
Tokens:
- Spacing: 4, 8, 12, 16, 24, 32
- Radius: 8
- Typography: H1 24/32, H2 18/26, Body 14/20
- Colors: text, muted, bg, primary, danger (no custom hex)

Components (must use): PageShell, Section, Card, Button, Input, Select, TextArea, FormRow, HelperText, Toast

Layout rules:
- Page padding: 24 desktop, 16 mobile
- Section spacing: 24
- Card padding: 16
- Grid: 12 cols desktop, 4 cols mobile, gap 16

Do:
- Reuse components and tokens only
- Keep labels above inputs, helper text below

Do not:
- Invent new spacing values, font sizes, or one-off CSS
- Mix different button heights or input styles

If a new component is needed:
- Extend an existing component pattern and document it in the output
- Do not create new visual styles outside tokens

仕様の後に、ドリフトを早期に検出するいくつかの受け入れチェックを加えてください:

  • 「上記トークンのみを使い、インラインスタイルを使わない」
  • 「すべてのフォームは FormRow を使い、同じ方法でエラーを表示する」
  • 「新しいコンポーネントを追加する場合は Card と既存のタイポグラフィに基づくこと」

チャットベースのジェネレーターを使うなら、この仕様をリクエスト間で安定させてください。毎回変えると意味がありません。

画面の一貫性を保つワークフロー

何も生成する前に UI 契約を書きます:小さなトークンセット(スペーシング、タイプ、カラー、半径、シャドウ)と短いコンポーネント目録(Button, Input, Select, Card, Modal, Table, Toast)。トークンやコンポーネントが欠けていると、モデルはそれを発明して UI がドリフトします。

次に、ルールを検証するリファレンス画面を一つ作ります。フォーム中心のページは良いストレステストです。ヘッダ、ヘルパー、検証エラー、プライマリ/セカンダリボタン、成功トーストを含むためです。その画面を基準として扱います。

そこから、新しい画面は既に定義したもので構成して作っていきます。「新しいスタイリングを」と頼むのではなく、同じ Card、同じスペーシングスケール、同じタイポグラフィ段階、同じフィールドパターンを使うように依頼します。

シンプルなワークフロー:

  • トークンとコンポーネントルールを確認して、機能単位で固定する
  • 1つのリファレンス画面を生成して、仕様を調整して理想の状態にする
  • 新しい画面はコンポーネントを再利用して作る(再スタイリングしない)
  • 一貫性をレビューしてからルール(トークン/コンポーネント)を変更し、個別のピクセルをいじらない
  • 次の機能で再利用できるスニペットとして仕様を保存する

「Search users」画面のスペーシングがリファレンスよりタイトになっていたら、個別にマージしてマージ先を直すのではなく、スペーシングトークンや Card のパディングルールを1回更新して再生成してください。

Koder.ai を使っている場合、スナップショットとロールバックが役立ちます:ベースラインをロックし、安全に実験して、変更がドリフトを招いたらすぐ戻せます。

避けるべき一般的なミス

Reactコードベースを所有する
生成したものをエクスポートして、トークンシステムをリポジトリに置いて配信します。

トークンとルールを「提案」扱いにするのが一番速く一貫性を失う方法です。小さな例外が積み重なって新しい画面ごとに倍増します。

よくある落とし穴の一つは、プロジェクト途中でスペーシングスケールを変えることです。初期の画面は 8, 16, 24 を使っていたのに、新しい画面が「こっちの方が良く見える」と 10 や 18 を導入すると、ドリフトが許されることになりますし、古い画面は更新されません。

別のドリフト源は、ジェネレーターに新しいコンポーネントスタイルを発明させることです。「ボタンのバリアントだけを許可する」と明記しないと、新しい画面で別の角丸や入力パディングが作られてしまいます。

状態(Loading、Empty、Error)も頻繁に見落とされます。後付けすると、通常は急いで作られたパターンが他と合いません。

また「soft shadows でモダンに」といった曖昧な指定は避け、代わりに「エラーはフィールド下に表示」「無効ボタンでもフォーカススタイルは残す」「Enter は最後のフィールドでのみ送信」といった具体的で再現可能な振る舞いルールを与えてください。

プロンプトに貼る軽量のガードレールを作るなら短くします:

  • 既存画面と同じスペーシングスケールとタイポトークンを使い、新しい値は発明しない
  • 承認済みコンポーネントとバリアントのみを使う
  • ローディング、空、エラー状態を同じトークンで含める
  • 視覚だけでなく振る舞い(検証、フォーカス順、送信、無効)も明確に指定する

マージ前の簡単な一貫性チェックリスト

生成された画面をマージする前に、2分で目を通してください。

スペーシングから始めます。13px のようなランダムな値や「合わせるために加えた」一時的なマージンを探してください。トークンを使っているなら、すべてのギャップが承認されたセットから来ているはずです(ガター、カードパディング、フォームフィールド間の間隔を含む)。

次にタイポグラフィをタイプスケールと照らし合わせます。見出しは予測可能に段階を踏んでいるか。類似のセクション間で本文のサイズが切り替わっていないか。行の高さも重要です、特に設定ページのような密な画面では。

ボタンをチェックします。バリアントとサイズはルール通りか:主要アクションには primary、重要度の低いアクションには secondary、削除には danger。ボタン高さ、アイコンの配置、ラベルスタイルが一致しているか。

フォームは構造の一貫性が重要です。ラベルの位置、必須表示のルール、ヘルパーとエラーの競合がないか、エラーが一貫した場所に出るか。

短いチェックリスト:

  • スペーシングはトークン値のみ(ランダム数値なし)
  • タイポグラフィはタイプスケールに一致(行の高さ含む)
  • ボタンは同じバリアントとサイズに従う
  • フォームはラベル/ヘルパー/エラーの1つのパターンを使う
  • 状態がある:hover, focus, disabled, loading, empty

最後にモバイルでの簡単な確認を行います。幅を狭めてレイアウトが新しいフォントサイズやスペーシングを勝手に作り出さずに順応するかを確認してください。

例:スタイルドリフトなしで小さな React 機能を生成する

UIから実際のアプリへ
スペーシング、タイプスケール、コンポーネントルールを確認したらアプリをデプロイしてホストします。

3つの画面(Profile、Preferences、Confirm)と後で Settings ページがあるシンプルなオンボーディングフローを想像してください。各画面は別々のランで生成されても、同じデザイナーが作ったように見えることを望んでいます。

何も生成する前に、小さなトークンセットといくつかのコンポーネントルールを提供します:

TOKENS
- spacing: xs=4, sm=8, md=12, lg=16, xl=24
- radius: sm=8, md=12
- type: body=14/20, title=20/28, label=12/16
- layout: pageMax=960, sectionGap=24, fieldGap=12

COMPONENT RULES
- Page: max width=pageMax, padding=xl, sectionGap between blocks
- Card: padding=lg, radius=md
- Field: label above, helper below, fieldGap between fields
- Button row: primary on right, gap=sm
- Errors: shown under field, same copy style, no alerts

これで “Profile” と “Preferences” を別々に生成しても、両方とも Page、Card、Field、Button row を定義どおりに使うため、同じマージン、ラベル間隔、ボタン配置になります。Confirm ステップは読み取り専用テキストが多くても収まります。

フォームの挙動はドリフトが忍び込みやすい場所なので、一度定義して再利用してください:有効になるまで送信ボタンを無効にする、エラーは blur または submit 後のみ表示、Enter は最後のステップでのみ送信、Back ボタンは既に入力した値を消さない。

新しい UI 部品が必要になったら、モデルにその場で即興させないでください。1つルールを追加し、再生成して再利用を意識します:

  • ProgressStepper: height=32, spacing=md, current step bold, completed steps clickable

次の一手:一貫性をデフォルトにする

トークンとルールを実際に使う再利用可能な仕様にします。貼るには長すぎると守られないので、実用的な仕様は短くまとめてください。

一般的に含めるべきは:トークン表(spacing、type、radii、colors)、短いコンポーネントルールセット(buttons、inputs、cards、headings)、フォーム挙動ルール(検証タイミング、エラー、disabled/loading)、レイアウトのデフォルト(ページパディング、最大幅、セクション間隔)、そして短い「やってはいけないこと」リスト(ランダムなマージン、アドホックなフォントサイズ)です。

そして1つの習慣を作ります:個別のピクセルを先に変えるのではなく、まず仕様を更新すること。

Koder.ai (koder.ai) を使っているなら、planning mode は仕様を再確認するのに良い場所です。代替案を試したいときは snapshots と rollback によって安定したベースラインを失わずに実験できます。

よくある質問

Why do generated React screens look inconsistent even when I ask for “modern styling”?

各画面のリクエストがその都度「ゼロからの開始」になるためです。共有システムを与えないと、ジェネレーターは不足している細部を推測して埋めます—スペーシング、フォントサイズ、ボタンのパディング、影、フォームの振る舞いなど。小さな差が積み重なって全体の一貫性が失われます。

What are design tokens in plain English, and why do they help?

デザイントークンは、スペーシング、フォントサイズ、色、角丸などの名前付きで再利用可能な値です。

「快適なパディング」の代わりに space-md のようなトークンを使います。名前が安定していれば、すべての画面で同じ決定が再利用されるため、UIのドリフトが止まります。

What’s a good “small but complete” token set to start with?

目に見えるドリフトを引き起こす要素だけをカバーする小さめのセットから始めてください:

  • Spacing: 4, 8, 12, 16, 24, 32
  • Type: 1つのボディサイズ + 2つの見出しサイズ + 固定の行の高さ
  • Colors: text, muted, bg, surface, border, primary, danger
  • Radius: 例:8 と 12 の2値
  • Elevation: none, small, medium

新しい値が必要な場合は、10px や 18px をそのまま導入するのではなく、最も近いトークンに丸めてください。

How do I write a prompt that keeps every generated screen consistent?

各リクエストの先頭に小さなUI仕様ブロックを置き、それを契約のように扱ってください:

  • トークンを列挙(spacing / type / colors / radius)
  • 許可するコンポーネントを列挙(Button, Input, Card 等)
  • ハードルールを追加(新しい spacing 値や一時的な CSS を禁止)

その下に画面の説明を置きます。重要なのは、この仕様を画面ごとに変えずに維持することです。

What component rules prevent the most UI drift?

トークンは数値を決めます。コンポーネントルールは習慣を決めます。以下のようなルールが有効です:

  • Button: 固定の高さ、パディング、少数のバリアント(primary/secondary/danger)
  • Inputs: 統一された高さ、ラベル間隔、ボーダー/フォーカススタイル
  • Cards: 固定のパディングと予測できるヘッダ/ボディ間の間隔
  • Layout: 標準のページパディングとセクション間隔

正当な理由がないバリアントは標準にフォールバックする、というルールが有効です。

What’s the best default for form validation timing and error placement?

一つのパターンを選び、どこでもそれを崩さないことです:

  • ラベルは常にフィールドの上(プレースホルダーをラベル代わりに使わない)
  • ヘルパーテキストは常にフィールドの下(エラーには使わない)
  • エラーはフィールド直下の1行で表示
  • エラー表示タイミング:フォーカスを外したとき(blur)または送信時に表示し、変更時に消す/更新する

これで「ある画面は blur で検証、別の画面は送信時のみ」といった不一致を避けられます。

Which UI states should I standardize so screens don’t feel random?

次のような状態ルールを決めておくと画面がバラバラに見えません:

  • Loading: コントロールを無効化し、スピナーを表示、幅を安定させる
  • Disabled: コントラストを下げるがホバー/フォーカスは完全に消さない
  • Empty state: 短いメッセージ+明確なアクション
  • Errors: フィールド内にインラインで表示し、レイアウトの大きな変化を避ける

状態を指定しないと、画面ごとに勝手なパターンが生まれます。

What should I do when a screen needs a new component that isn’t in my list?

即興でスタイルを作らせないことです。新しいコンポーネントは、既存のパターンの拡張としてドキュメント化して追加してください:

  • 既存のトークンと親しみのあるコンテナ(多くは Card)に基づく
  • サイズ、間隔、状態を既存のトークンで定義
  • 将来の画面用に承認済みコンポーネントリストに加える

トークンで説明できないなら、それはたいていカスタムすぎてドリフトの原因になります。

What workflow keeps consistency when I generate screens over multiple sessions?

システムをストレステストする“リファレンス”画面(フォーム中心のページが良い)を一つ作っておき、それを基準にして同じ仕様を再利用します。

Koder.ai を使う場合は、planning mode で仕様を再確認し、snapshots と rollback を使って安定したベースラインを保ちながら実験できます。

What’s a fast checklist to catch inconsistency before I merge a generated screen?

マージ前の短いスキャンチェックリスト:

  • スペーシングはトークン値のみ(ランダムな数値はないか)
  • タイポグラフィがタイプスケールに合っている(行の高さ含む)
  • ボタンがルール通りのバリアントとサイズか
  • フォームが一貫したラベル/ヘルパー/エラー構造を使っているか
  • 状態が存在する:hover, focus, disabled, loading, empty

ズレがあれば個別のマージ修正をせず、仕様/トークンを更新して再生成するのが推奨です。

Related posts