design-component について
design-componentは、プロジェクトのデザイン、トークン、アクセシビリティ規則に沿ったコンポーネント仕様を作成するスキルです。完了と判断する前に、各状態の表示やレスポンシブ動作も確認します。
主な機能
- 01プロジェクト形式のコンポーネント仕様
- 02構造、バリエーション、サイズ、状態の定義
- 03トークンとアクセシビリティの対応付け
- 04表示状態とレスポンシブ動作の検査
- 05目視確認と操作の検証
活用シーン
- 01既存のデザインキットに沿った仕様作成
- 02操作状態ごとのコンポーネント検査
- 03アクセシビリティとレスポンシブ表示の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ plugin87/ ux-ui-agent-skills --skill design-componentインストール後、Claude Code で/design-componentを入力して読み込まれたか確認します。
詳細
スキル:デザインコンポーネント
ステップ 0:キットはありますか? このスキルはキットのファイルを読み込みます。一度確認してください:
ls ${CLAUDE_SKILL_DIR}/../../../tokens >/dev/null 2>&1 && echo KIT_OK || echo KIT_MISSINGKIT_MISSINGの場合、インストールされているのはスキルフォルダーのみです。これはnpx skills addを実行したときの状態です。その旨を明確に伝え、npx ux-ui-agent-skills initまたはプラグインのインストールを案内して、そこで終了してください。開けなかったファイルの内容を推測してはいけません。
プロジェクトのフォーマットに沿った、完全なコンポーネント仕様を作成します。
手順
${CLAUDE_SKILL_DIR}/../../../.claude/rules/components.mdの「Component Quality Bar」(8 状態の表)と「Atomic Design」を読みます。常時適用される 8 状態の表は${CLAUDE_SKILL_DIR}/../../../CLAUDE.mdの Non-Negotiables にあります。- 既存の仕様がないか確認します:
${CLAUDE_SKILL_DIR}/../../../components/atoms.md、molecules.md、organisms.md、templates.md、navigation.md、feedback.md、forms-advanced.md、overlays.md。既存の仕様フォーマットに合わせます。 - ARIA パターンを
${CLAUDE_SKILL_DIR}/../../../accessibility/aria-patterns.mdから、コントラストとターゲットのルールを${CLAUDE_SKILL_DIR}/../../../accessibility/wcag-checklist.mdから取得します。 - すべての値をトークンに対応付けます(
${CLAUDE_SKILL_DIR}/../../../tokens/*.json):サイズにはsizing.json、状態にはstates.jsonを使います。 ${CLAUDE_SKILL_DIR}/../../../taste/design-taste.mdのビジュアル判断基準(状態、フォーカス、雑な仕上がりを避けること)を適用します。- 任意のクイックスタート:
python3 ${CLAUDE_SKILL_DIR}/../../../scripts/scaffold_component.py "<Name>"を実行してひな形を出力し、その後に仕上げます。
出力
次を含む仕様:構造図、バリエーション表、サイズ表、該当する 8 状態すべて、トークンの対応付け、アクセシビリティ(ロール/キーボード/スクリーンリーダー)、および ${CLAUDE_SKILL_DIR}/../../../frameworks/adapter-protocol.md に従ってレンダリングする旨の注記。
正確性:すべての状態を検証してください(コードを作成する場合は必須)
コンポーネントが「正しい」と言えるのは、初期状態だけでなく、すべてのバリエーション × 状態が正しくレンダリングされる場合のみです。状態ハーネスを作成します。該当する各状態(default、hover、focus、active、disabled、loading aria-busy、error aria-invalid、selected aria-pressed/aria-selected)× 各バリエーションのコンポーネントを、1 つの HTML ファイルにレンダリングします(${CLAUDE_SKILL_DIR}/../../../examples/component-states/button.html を参照)。次にゲートを実行し、実際の出力を報告します(CLAUDE.md → Verification Protocol):
node ${CLAUDE_SKILL_DIR}/../../../scripts/gate.mjs verify_states.mjs <harness> [--dark]:default/hover/focus におけるすべての要素のコントラストnode ${CLAUDE_SKILL_DIR}/../../../scripts/gate.mjs axe_audit.mjs <harness> [--dark]:ARIA/role/name/label の正しさnode ${CLAUDE_SKILL_DIR}/../../../scripts/gate.mjs measure_render.mjs <harness> [--dark]:すべてのテキスト要素の AA 準拠- オーバーレイ/モーダルの場合も実行:
node ${CLAUDE_SKILL_DIR}/../../../scripts/gate.mjs verify_focustrap.mjs <harness> --open=<trigger>コンポーネントが「完了」と言えるのは、ライトモードとダークモードの両方で、すべての状態が合格した場合のみです。ゲートで証明せずに、状態が正しいと主張してはいけません。
ゲートはコントラストとアクセシビリティを検証します。ピクセルは検証しません。レンダリングして確認してください。
UI が目に見えて壊れていても、コントラストと axe のゲートは通過します。たとえば、切り替わらないチェックボックス、枠の下部に寄ったダッシュ、線の太さが一致しないチェックマークと未確定状態のダッシュ、太すぎるコントロールなどです。完了を宣言する前に、すべての状態について、操作後も含めて、ハーネスのスクリーンショットを撮って確認してください。Playwright + システムの Chrome:
const b = await chromium.launch({channel:'chrome'});
const p = await b.newPage({deviceScaleFactor:4});
await p.goto('file://'+abs); await p.addStyleTag({content:'*{transition:none!important}'});
await p.mouse.move(2000,2000); // park pointer OFF the component
await p.locator('.stack').first().screenshot({path:'/tmp/x.png'});
PNG を確認してください。次に、特に以下を確認してください。
- 機能:各インタラクティブ要素をクリックし、状態が実際に変化したことをアサートします(
await loc.click(); expect(await loc.isChecked()))。オーバーレイボックスが実際の<input>を覆うカスタムコントロールは、ボックスにpointer-events:noneを設定するか、クリックを転送する親の<label>で囲まない限り、切り替わりません。 - ジオメトリ:グリフが重なったりずれたりせず、中央に配置されていること。
display:gridのボックスにopacity:0の兄弟要素と::afterがあると、疑似要素は2行目に配置されます。非表示の兄弟要素にdisplay:noneを使うか、子要素を1つのコンテナにまとめてください。 - 線の太さの統一:チェックマークと未確定状態のダッシュは、同じ描画方法(1つの
<svg>に2つの<path>を置き、状態に応じて切り替え、同じstroke-widthを使用)にします。SVG のチェックと CSS の::afterによる長方形を組み合わせてはいけません。線の太さが異なって見えるためです。 - トランジションによる見え方:トランジションを無効にせずにスクリーンショットを撮ると、クリック直後のコントロールがアニメーションの途中で半透明に見えることがあります。これはバグではありません。状態を判断する際は、必ずトランジションを無効にし、ポインターを別の場所に移動してください。
ファイル間での一貫性は必須です。 同じコンポーネント(例:チェックボックス)は、すべてのハーネスやページでバイト単位まで同一の CSS とマークアップを使用してください。form-controls では細く見え、data-table では太く見える(ネイティブの accent-color を使用している)チェックボックスはバグです。パターンを共通化し、そのまま再利用してください。
検証済みのカスタムチェックボックス/ラジオボタンのパターン(細い線、トークン駆動、ゲート検証済みかつ目視確認済み)
実際の <input> を下に配置します(ネイティブのアクセシビリティとキーボード操作を維持します)。pointer-events:none を指定した .box のオーバーレイを描画し、1つの <svg> 内にあるチェック用とダッシュ用の2つの <path> を :checked/:indeterminate で切り替えます。border-strong は1.5px、角丸は .25rem、グリフは .62rem、stroke-width は2で、端は丸くします。参考:${CLAUDE_SKILL_DIR}/../../../examples/component-states/form-controls.html と data-table.html(すべて選択するチェックボックスは indeterminate を使用)。ネイティブの accent-color は太く描画されます。デザインの基調が「細い線」の場合は使用しないでください。
レスポンシブ対応:すべてのコンポーネントで横スクロールなし(ゲート検証済み)
モバイルファーストで構築してください。縮小できない固定ピクセル幅はバグです。node ${CLAUDE_SKILL_DIR}/../../../scripts/gate.mjs verify_responsive.mjs <file|dir> を実行してください。各ハーネスを幅280/320/414pxで読み込み、横方向のオーバーフローがあると失敗します。よくある原因と対処法は次の4つです。
- 固定の
inline-size:Npx→inline-size:100%;max-inline-size:Npx(幅を固定せず、上限を設定します)。 <ul>/<ol>のデフォルトのインライン方向の開始側パディング40px(*{margin:0}のリセットではパディングは解除されません)→ すべてのリストにpadding:0;margin:0を設定します。これにより、リストと隣接するブロックの端がずれて幅が異なって見える問題も防げます。同じ対処法です。- 折り返さないフレックス行(パンくずリスト、ステッパー、タブ)→
flex-wrap:wrapを指定するか、タブの場合はoverflow-x:autoと.tab{flex:none}を使用します。 - ビューポートより大きい最小幅を持つ
grid minmax(Npx,1fr)→minmax(min(Npx,100%),1fr)を使用します。
モーション:トークンを使い、適切にイージングし、動くものをアニメーションさせます
時間とイージングはトークンです(テーマの --duration-fast|normal|slow、--ease-out|in|in-out|emphasized、--transition-micro = fast ease-out)。ミリ秒や曲線をハードコードしないでください。開閉を切り替えるコンポーネントでは、シェブロンを回転させるだけでなく、高さをアニメーションさせます。hidden/display:none だけで折りたたむと、「硬く、トランジションなし」に見えます。JSで高さを測定しないスムーズな高さ変更パターンでは、コンテンツをオーバーフローをクリップする内側の要素で囲み、グリッドトラックをアニメーションさせます。
.panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--duration-base) var(--ease-spring)}
.panel.open{grid-template-rows:1fr}
.panel > .inner{overflow:hidden;min-block-size:0}
アクセシビリティを維持します。展開時は hidden を削除し、次のフレームで .open を追加します。折りたたみ時は .open を削除し、transitionend で hidden を設定します。参考:${CLAUDE_SKILL_DIR}/../../../examples/component-states/overlays.html のアコーディオン。必ず @media(prefers-reduced-motion:reduce){…transition:none} を尊重してください。
レイアウト:スペースを埋め、AI生成のような空白だらけの領域を作らない
- カードグリッドでは
auto-fillではなく、必ずauto-fitを使います。auto-fillは空の見かけ上のトラックを残すため、カードが3枚だと左側に固まり、右側に空白ができます。auto-fitは空のトラックを折りたたみ、カードが広がって行を埋めます。必ずrepeat(auto-fit,minmax(min(Npx,100%),1fr))を使います。 - 横並びのパネルの高さをそろえる場合:グリッドに
align-items:stretchを指定し、さらに高さの低いパネルの本文を伸ばします。.panel{display:flex;flex-direction:column}を設定し、内側の領域にflex:1を指定します。block-size:%(例:チャートの棒)でサイズ指定された子要素には、確定した高さを持つ祖先(flex:1のボックス、または明示的な高さ)が必要です。そうでない場合、% は 0 として解決され、要素が潰れてしまいます。棒をflex:1の.barboxで囲み、棒にはその高さに対するblock-size:%を指定します。 - メイン領域の80%が空白だと、機械生成のように見えます。 ウィジェットを1つだけ置くのではなく、実際にありそうなコンテンツ(統計の行+アクティビティリスト+チャート)でダッシュボードを埋めます。意図的な情報密度が、「デザインされたもの」と「AIの粗雑な生成物」の違いです。
- ツールバー/ヘッダー末尾の余白:
max-inline-sizeで上限を設けたflex:1のフレックスアイテムは、それ以上伸びなくなり、最後のアイテムの後ろに空白が残ります。右側の要素群を押し出すには、その先頭要素にmargin-inline-start:autoを指定します。 - モバイルナビゲーションは重ならないようにします。 サイドバーとメイン領域を同じグリッド領域に配置すると、サイドバーを開いたときにコンテンツの上に表示されます。モバイルではシェルを
display:blockに切り替え、サイドバーを開くとメイン領域が下に押し出されるようにします。参考:${CLAUDE_SKILL_DIR}/../../../examples/component-states/app-shell.html。
アイコン:本物の lucide を名前で参照します(パスを手描きしない)
手作業で近似したSVGパスデータは、壊れたグリフとして描画されます(ヘルプの「?」がドットになり、設定がハンバーガーになりました)。lucide のパスをそのまま使い、注入された <symbol> スプライトを介して名前で参照します。${CLAUDE_SKILL_DIR}/../../../examples/component-states/icons.js では各アイコンを一度だけ定義し、<svg class="ico" aria-hidden="true"><use href="#i-NAME"/></svg> で使用します。使用箇所ごとにパスを複製せず、ネットワーク接続も不要なため、オフラインでもゲートセーフです。新しいアイコンは icons.js に一度だけ追加し、マークアップに生のパスを貼り付けないでください(インラインの lucide は、パスを lucide からそのままコピーした場合に限り使用できます)。.ico{stroke:currentColor;fill:none;stroke-width:2} を指定し、色は currentColor で設定します。
グラフィカルなコントロール/アイコンのみのコントロール(3:1、テーマに左右されない)
テキストのないコントロール(カルーセルのドット、ケバブメニュー、アイコンボタン)に求められるコントラスト比は 3:1(WCAG 1.4.11)であり、4.5:1 ではありません。verify_states は、要素に直接のテキストノードがない場合にこれを自動で適用します。次の2つの落とし穴を検出します。
- 空の
<button>は、テーマに関係なくUAのcolor:buttontext(≈黒)を保持します。そのため、colorに実際のインジケーターの色を設定し、currentColorで見た目を制御します(例:ドットは::before{background:currentColor})。これにより、ゲートが実際の色を測定できます。 - テーマで変化するトークン(
--color-chart-N、--color-surface-brand)はライトモードとダークモードで反転します。これらに白いテキストやティール色のインジケーターを置くと、一方のモードでは合格し、もう一方では不合格になります。アバター、アクティブなドット、選択状態には、ダークモードを考慮した値([data-theme="dark"]で上書き)か、モードに依存しないトークン(モードに応じて変化する--color-action-primary、--color-text-link)を使います。