ui-engineering について
ui-engineeringは、フロントエンドコンポーネントを構築する開発者向けの実践スキルです。実装前に、構造や状態、アクセシビリティ、レスポンシブ設計、フォーム、パフォーマンスを検討する指針を示します。
主な機能
- 01コンポーネント構成と設計の確認
- 02GridとFlexboxの選択指針
- 03状態の配置原則
- 04レスポンシブ設計の手法
- 05アクセシビリティとフォームの基準
- 06モーションと描画性能の指針
活用シーン
- 01コーディング前の構造と状態の設計
- 02ページレイアウト方式の選定
- 03レスポンシブなフォームやナビゲーションの構築
- 04インタラクティブ要素のアクセシビリティ確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ noobygains/ godmode --skill ui-engineeringインストール後、Claude Code で/ui-engineeringを入力して読み込まれたか確認します。
同梱ファイルcomponent-patterns.md
詳細
UI エンジニアリング
概要
場当たり的なマークアップではなく、規律あるアーキテクチャで UI コンポーネントを構築します。
基本原則: レイアウト戦略、状態の所有者、アクセシビリティへの配慮、レスポンシブな動作など、すべてのコンポーネントに関する判断は、確立されたパターンに従います。UX Patterns スキルは、何を構築するかを示します。このスキルは、構造的な整合性を保ちながら、どのように構築するかを示します。
前提条件: まず godmode:ux-patterns を呼び出して、適用するパターンとトークン値を特定してください。このスキルでは、トークンとパターンがすでに確立されていることを前提としています。
使用する場面
必須となる場合:
- フロントエンドコンポーネントを構築するとき
- Grid と Flexbox のどちらを使うか選択するとき
- 状態をどこに保持するか決めるとき
- レスポンシブなブレークポイントの動作を設計するとき
- フォーム、データ可視化、ナビゲーション、オーバーレイを構築するとき
- モーションやトランジションを導入するとき
次の工程として実施:
- デザイントークンが確立された後(ux-patterns)
- 対象となる UI パターンが特定された後(ux-patterns)
最優先指針
NO COMPONENT WITHOUT STRUCTURE, STATES, AND ACCESSIBILITY DEFINED FIRST
コンポーネントのコードを書く前に、セマンティックな構造(適切な HTML 要素)、すべての視覚状態(空、読み込み中、エラー、データあり、無効)、アクセシビリティ要件(ARIA 属性、キーボード操作、コントラスト比)を確立します。
コンポーネントのアーキテクチャ
設定より合成
巨大でプロパティが多いブロックではなく、小さく合成可能な単位からコンポーネントを組み立てます。
``` Dialog DialogHeader DialogTitle DialogDescription DialogBody DialogFooter ``` それぞれの単位は単独で利用でき、独立してスタイルを設定できます。 ``` Dialog (title, description, body, footer, headerAlign, footerAlign, showCloseButton, variant, size, overlayOpacity, titleSize, ...) ``` プロパティが肥大化し、保守が困難で、拡張もできません。実装前チェックリスト
コンポーネントを書く前に、以下を確認します。
- セマンティクス -- 適切な HTML 要素は何ですか?(
div onClickではなくbutton、div className="nav"ではなくnav) - プロパティ -- 必要最小限のインターフェースは何ですか?設定するのではなく、合成できますか?
- 状態 -- デフォルト、ホバー、フォーカス、アクティブ、無効、読み込み中、エラー、空
- バリエーション -- どのような視覚的なバリエーションが必要ですか?(primary、secondary、ghost、destructive)
- サイズ -- サイズの段階は何種類ありますか?(sm、md、lg -- 最大 3~4 種類)
- レスポンシブ対応 -- 各ブレークポイントでどのように変化しますか?
- アクセシビリティ -- ARIA ロール、キーボードナビゲーションの経路、スクリーンリーダーへの通知
レイアウト戦略の選択
digraph layout_choice {
rankdir=TB;
q1 [label="What is being\narranged?", shape=diamond];
q2 [label="Single axis or\ntwo axes?", shape=diamond];
q3 [label="Are items\nuniform in size?", shape=diamond];
grid [label="Use CSS Grid\ngrid-template-columns\ngrid-template-rows", shape=box];
flex [label="Use Flexbox\nflex-direction\njustify/align", shape=box];
grid_auto [label="Use CSS Grid\nauto-fill/auto-fit\nminmax()", shape=box];
q1 -> q2;
q2 -> grid [label="two axes\n(rows AND columns)"];
q2 -> q3 [label="single axis\n(row OR column)"];
q3 -> grid_auto [label="yes\n(uniform cards)"];
q3 -> flex [label="no\n(nav items,\nform row)"];
}
CSS Grid -- 適切な場面
- ページレベルの骨組み(サイドバー+メインコンテンツ+補助領域)
- 均一なカードグリッド
- ダッシュボードの配置(メトリックタイル、チャート領域)
- 2 次元の制御が必要なレイアウト
- 行と列をまたいだ配置
パターンの例:
/* Self-adjusting card grid */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--space-6);
}
/* Dashboard scaffold */
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto 1fr;
gap: var(--space-6);
}
.dashboard .wide-chart { grid-column: span 2; }
Flexbox -- 適切な場面
- 1 行に並ぶナビゲーション項目
- フォーム入力とボタンを横並びにするグループ
- コンテナー内でコンテンツを中央揃えにする
- 1 つの軸に沿って幅が異なる項目を配置する
パターンの例:
/* Navigation row */
.nav-row {
display: flex;
align-items: center;
gap: var(--space-4);
}
/* Inline form group */
.inline-group {
display: flex;
align-items: flex-end;
gap: var(--space-3);
}
.inline-group .input-field { flex: 1; }
状態の所有者を決める戦略
digraph state_ownership {
rankdir=TB;
q1 [label="Where does this\nstate belong?", shape=diamond];
q2 [label="Consumed by\nmultiple components?", shape=diamond];
q3 [label="Server-originated\nor client-only?", shape=diamond];
q4 [label="Prop-drilling\nexceeds 3 levels?", shape=diamond];
local [label="Component-local state\nuseState / ref", shape=box];
server [label="Server state manager\nTanStack Query / SWR", shape=box];
context [label="Context / Provider\nReact Context / provide-inject", shape=box];
global [label="Global store\nZustand / Pinia / Signals", shape=box];
q1 -> q2;
q2 -> local [label="no\n(isolated component)"];
q2 -> q3 [label="yes"];
q3 -> server [label="server-originated\n(API responses,\ncached data)"];
q3 -> q4 [label="client-only\n(UI flags,\npreferences)"];
q4 -> context [label="no\n(2-3 levels)"];
q4 -> global [label="yes\n(application-wide)"];
}
基本原則:
- ローカル状態から始めます。根拠がある場合にのみ、より上位へ移します。
- サーバーデータはクライアント状態ではありません。専用のサーバー状態管理ライブラリを使って管理します。
- コンテキストは依存性注入(テーマ、認証コンテキスト)のために使い、高頻度の更新には使いません。
- グローバルストアは最後の手段であり、出発点ではありません。
レスポンシブデザインの手法
モバイルファーストで段階的に対応
モバイル向けのスタイルをベースラインとして記述し、画面幅の広いブレークポイントで段階的に複雑さを加えます。
/* Mobile baseline */
.wrapper {
padding: var(--space-4);
}
/* Tablet tier */
@media (min-width: 768px) {
.wrapper {
padding: var(--space-6);
max-width: 768px;
margin: 0 auto;
}
}
/* Desktop tier */
@media (min-width: 1024px) {
.wrapper {
padding: var(--space-8);
max-width: 1280px;
}
}
レスポンシブ対応の参考
| 要素 | モバイル | タブレット | デスクトップ |
|---|---|---|---|
| ナビゲーション | ハンバーガーメニューまたはボトムシート | タブバーまたは折りたたみ式サイドバー | 展開されたサイドバー |
| カードグリッド | 1列 | 2列 | 3~4列 |
| データテーブル | カード形式で縦に表示、または横スクロール | 列数を減らした全テーブル | 完全なテーブル |
| サイドバー + メイン | メインのみ。サイドバーはドロワー内 | アイコンのみのサイドバー | 完全に展開されたサイドバー |
| フォーム | 1列、入力欄は幅いっぱいに広げる | 1列、最大幅 560px | 関連するフィールドを2列で表示 |
| モーダル | 全画面シート | ビューポート幅の80%で中央に配置 | 最大幅 480px で中央に配置 |
| ヒーロー | 縦に配置(見出しの下に画像) | 縦に配置、文字を大きくする | 横に並べて配置 |
アクセシビリティ基準
すべてのインタラクティブ要素
- Tab キーで移動できる
- フォーカスリングが見える(
:focus-visible、アウトラインは最低2px) - Enter/Space(ボタン)または Enter(リンク)で有効化できる
- アクセシブルな名前がある(表示テキスト、
aria-label、またはaria-labelledby) - 無効状態ではタブ順から除外するか、
aria-disabledを適用する - モバイルではタッチターゲットが最小 44x44px を満たす
フォーム
- すべての入力欄に表示される
<label>がある(プレースホルダーだけでは不十分) - 必須フィールドを示す(アスタリスクと
aria-required="true") - エラーメッセージが入力欄に関連付けられている(
aria-describedby) - エラーは色だけでなく、アイコンとテキストを併用して示す
- 送信結果をスクリーンリーダーに通知する(
aria-live)
画像とアイコン
- 意味のある画像には説明的な
altテキストを設定する - 装飾用画像には
alt=""またはaria-hidden="true"を設定する - アイコンのみのボタンには
aria-labelを設定する - SVG アイコンには
role="img"とaria-label、またはaria-hidden="true"を設定する
色とコントラスト
- 通常のテキストはコントラスト比 4.5:1 を満たす(大きなテキストは 3:1:太字で18px以上、または24px以上)
- 情報を色だけで伝えない(アイコン、パターン、テキストを併用する)
- モーションは
prefers-reduced-motionに従う - ダークモードを提供する場合、配色は
prefers-color-schemeに従う
アニメーションとモーション
アニメーションを使う場面
- 状態遷移: ホバー、フォーカス、展開/折りたたみ、表示/非表示
- フィードバック: 成功、エラー、読み込みの進行状況
- 空間的な手がかり: コンテンツの表示元や消えた場所を伝える
アニメーションを使わない場面
- 機能的な目的のない装飾
- UI 遷移の継続時間が 300ms を超える場合
- ユーザー操作を妨げるモーション(強制的な待機)
- ユーザーが制御できない連続的な動き
タイミングの参考
| インタラクション | 継続時間 | イージング |
|---|---|---|
| ホバー時の反応 | 150ms | ease |
| ボタン押下 | 100ms | ease-out |
| モーダルの表示 | 200ms | ease-out |
| モーダルの終了 | 150ms | ease-in |
| ドロワーのスライド | 250ms | ease-out |
| フェードイン | 200ms | ease |
| ページ遷移 | 200-300ms | ease-in-out |
ユーザーのモーション設定に従う
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
フォームの設計
バリデーションの使いやすさ
- キー入力のたびにバリデーションしないでください。 フォーカスが外れたとき(フィールドを離れたとき)またはフォーム送信時に検証します。
- エラーは該当するフィールドの下にインラインで表示し、 サマリーバナーには表示しません。
- エラーのテキストで補助テキストを置き換えます。 両方を同時に表示しないでください。
- エラーは色とアイコンを併用して示し、 色だけで示さないでください(アクセシビリティ)。
- ユーザーの入力に応じて、入力内容が有効なフィールドの表示(チェックマークまたは緑色の枠線)を維持します。
- 送信中は送信ボタンを無効にし、読み込みインジケーターを表示します。
フィールド配置のガイドライン
- ラベルは入力欄の上に配置する(横に並べない。プレースホルダーだけにしない)
- 関連するフィールドは
<fieldset>と<legend>でまとめる - 必須フィールドにはアスタリスクと
(required)の両方を付け、スクリーンリーダーに伝える - 任意のフィールドには代わりに「(optional)」を表示してもよい
- フォームの最大幅:1列の場合は 480-560px
- 送信ボタンは中央ではなく左揃えにする(モーダル内を除く)
レンダリングのパフォーマンス
クリティカルパスの最適化
- ファーストビューのコンテンツを最初に読み込みます。 ファーストビューより下にあるアセットとスクリプトは遅延させます。
- ブランディング上、Web フォントが必須でない限り、本文にはシステムフォントを優先します。 Web フォントはレイアウトシフトを引き起こします。
- 画像の適切な扱い:
width/height属性を指定して(CLS を防止)、ファーストビューより下の画像にはloading="lazy"を設定し、レスポンシブな配信にはsrcsetを使用します。 - バンドルを意識します: インポートするライブラリはすべてコストになります。依存関係を追加する前に、バンドルへの影響を測定します。
遅延読み込み戦略
- ファーストビューより下の画像:
loading="lazy" - ルートモジュール:動的インポート/コード分割
- 重いコンポーネント(チャート、リッチエディター):操作時または表示時に読み込みます
- ファーストビューのコンテンツは遅延読み込みしないでください
よくある間違い
| 間違い | 修正方法 |
|---|---|
すべてに div を使う | セマンティック HTML(button、nav、main、section、article)を使用します |
| プレースホルダーだけをラベルとして使う | 常に表示される <label> 要素を用意します |
| あらゆる場所にピクセル値を直書きする | デザインシステムのトークン変数を使用します |
| コンテナーの幅を固定する | max-width とパーセンテージ、または自動マージンを使用します |
| フォーカスのアウトラインを削除する | アウトラインをなくすのではなく、:focus-visible をスタイル設定します |
| div にクリックハンドラーを設定する | インタラクティブな要素には <button> または <a> を使用します |
| レイアウトの間隔にマージンを使う | Grid または Flexbox で gap を使用します |
| アイコンライブラリ全体をインポートする | 個々のアイコンをインポートし、ツリーシェイキングを有効にします |
| ハッピーパスだけを実装する | ハッピーパスを構築する前に、すべての状態を設計します |
| デスクトップでしかテストしない | モバイルファーストでテストし、その後デスクトップで確認します |
統合
前提条件:
- godmode:ux-patterns -- 最初にトークンとパターンを確立する必要があります
補完スキル:
- godmode:design-integration -- 確立済みのデザインシステム内で作業する場合
- godmode:test-first -- コンポーネントテストはテストファーストの手法に従います
サポートファイル:
component-patterns.md-- アクセシビリティを組み込んだ再利用可能なコンポーネントの設計図