mcpField
C
components

components

github.com/paragonhqデザイン・UI044 回インストール5 週間前に更新
リポジトリ

components について

components は、一貫性とアクセシビリティを備えた UI コンポーネントを設計するスキルです。複雑な動作にはヘッドレスプリミティブを用い、API、状態、スタイル、テストを扱います。

主な機能

  1. 01複雑な操作にプリミティブを選択
  2. 02操作状態を網羅的に定義
  3. 03制御・非制御 API を構成
  4. 04デザイントークンでスタイルを指定
  5. 05キーボード・画面幅・支援技術でテスト

活用シーン

  1. 01アクセシブルなダイアログやメニューを設計
  2. 02コンポーネントの状態網羅性を点検
  3. 03コンポーネント API とスタイルを標準化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/paragonhq/stealth-skills --skill components

インストール後、Claude Code で/componentsを入力して読み込まれたか確認します。

詳細

コンポーネント

コンポーネントは契約です。どこでも同じように見え、どこでも同じように動作し、信頼するために中身を確認する必要がありません。生成されたコンポーネントのほとんどは、3つ目の条件を満たしません。

プリミティブか、自前で実装するか?

動作を伴わないものだけ自前で実装します。カード、バッジ、区切り線、統計値などです。フォーカス管理、キーボード操作、配置、ARIA のいずれかを伴うものには、ヘッドレスプリミティブを使います。

必要なものプリミティブ自前で実装しない理由
ダイアログ、シート、ポップオーバー、メニュー、セレクト、ツールチップ、タブ、トグル、スライダー、コンボボックスBase UI, Radix, React Aria, Arkフォーカストラップ、エスケープキー、外側クリック、タイプアヘッド、ARIA、ポータル、衝突処理
ドラッグ可能なドロワーVaulジェスチャーの物理挙動、スナップポイント、スクロールロック
トーストSonnerスタック表示、スワイプ、タイマー、アクセシビリティ通知
コマンドパレットcmdkフィルタリング、キーボード操作、グループ、アクセシビリティ
数値アニメーションNumberFlow数字の列、ロケールに応じた書式設定
データテーブルTanStack Table (headless)並べ替え、仮想化、列の状態
フォームネイティブ機能 + 小規模なバリデーションライブラリ制約 API、最初のエラーへのフォーカス

ルール:ARIA Authoring Practices に仕様がある場合は、それを実装するプリミティブを使います。

状態マトリクス

すべてのインタラクティブコンポーネントは、完成前に以下の状態をすべて網羅します。書き出し、確認した項目にチェックを付けます。

状態見た目動作
デフォルト——
ホバー(ホバー操作が可能なデバイスのみ)1段階の変化—
フォーカス可視リング—
アクティブ / 押下scale .97 または暗くする—
選択済み / チェック済み / オン塗りつぶしまたはアクセントaria-* に反映
無効不透明度 50%、ホバーなしフォーカス不可、タブ順に残す必要がある場合は aria-disabled
処理中 / 読み込み中同じ幅のままスピナーを表示入力を無視、aria-busy
エラー / 無効な値枠線 + メッセージaria-invalid、メッセージへの aria-describedby
空(コンテナの場合)1行 + 1つのアクション—
読み取り専用テキストスタイル、操作可能であることを示す要素なし—

サイズは2つか3つ(sm、md、lg)にします。バリアントは primary、secondary(枠線)、ghost、danger にします。7つにはしません。outline-gradient-xl にもしません。

props ではなく状態からスタイルを決める

コンポーネントと CSS の不一致を防ぐため、スタイルは要素の実際の状態を参照します。

<button data-state={open ? "open" : "closed"} data-size={size} data-variant={variant} aria-busy={busy || undefined} />
.button[data-variant="primary"] { ... }
.button[data-state="open"] { ... }
.button[aria-busy] { ... }
.button:where(:disabled, [aria-disabled="true"]) { ... }

プリミティブはすでに data-state、data-highlighted、data-disabled を出力します。これらを React の状態に複製するのではなく、そのままスタイルに使います。

コンポジションによる API 設計

40個の props を持つ1つのコンポーネントより、組み合わせて使える小さな部品が優れています。

<Dialog.Root>
  <Dialog.Trigger asChild><Button>Rename</Button></Dialog.Trigger>
  <Dialog.Content>
    <Dialog.Title>Rename file</Dialog.Title>
    <Dialog.Description>…</Dialog.Description>
    <Dialog.Close asChild><Button variant="secondary">Cancel</Button></Dialog.Close>
  </Dialog.Content>
</Dialog.Root>

ガイドライン:

  • asChild / render を使い、ユーザーの要素をラップするのではなく、その要素に動作を適用します。
  • 残りの props はルート要素にスプレッドします。className と style を引き継ぎ、ref も転送します。
  • 状態にはブール値の props、バリアントには文字列のユニオン型を使います。ネイティブ属性と type="..." が衝突しないようにします。
  • 制御ありと制御なしのどちらでも動作するようにします。value + onValueChange、または defaultValue を使います。どちらか片方だけにはしません。
  • イベントハンドラーには onX という名前を付け、ネイティブイベントまたは単純な値を受け取るようにします。独自に作った合成オブジェクトは渡しません。
  • isLoading と loading のように重複させません。名前は1つに統一し、どこでも同じものを使います。

値ではなくトークンを使う

コンポーネントでは、色、角丸、余白、イージング、時間にトークンを参照します。コンポーネントにインラインの16進数カラー、0.3s、cubic-bezier が含まれているなら、システムから分岐しています。

角丸は階層ごとに統一します。入力欄とボタンは6〜8px、カードとポップオーバーは8〜12px、ダイアログは12〜16px、ピルは最大値にします。入れ子になった角丸は小さくします。12px のカード内にある要素には、12px ではなく8pxを使います。

配置レイヤー

ポップオーバー、メニュー、ツールチップは body にポータルし、衝突処理を行いながらトリガーを基準に配置し、解決された配置に応じて transform-origin を設定します。表示時には約160msの拡大 / 不透明度アニメーションを使います。ダイアログは中央に配置し、背景を表示して、レイアウトをずらさないスクロールロックを適用します(スクロールバー分を補正します)。フォーカスはダイアログ内に移動し、閉じたときに元の場所へ戻します。

Z-index は単なる数値ではなく、スケールです。--z-sticky: 10; --z-dropdown: 20; --z-dialog: 30; --z-toast: 40。50を超える値があれば、それは不具合です。

入力

  • 視認できるラベルを付けます。プレースホルダーはヒントであり、ラベルではありません。
  • エラーメッセージは入力欄の下に表示し、aria-describedby で関連付けます。キー入力のたびに表示するのではなく、フォーカスが外れたとき、または送信時に表示します。
  • 高さは、同じサイズのボタンの高さに合わせます(sm は 32px、md は 36px、lg は 40px)。
  • タッチ操作ではフォントサイズを 16px にします。そうしないと、ブラウザーでズームされます。
  • inputmode と autocomplete を設定します。スマートフォンでは enterkeyhint も設定します。
  • 入力欄内のアイコンは、ボタンでない限り装飾要素です。ボタンの描画サイズが 16px でも、タップ領域は 32px にします。

リストと行

  • 行全体をクリック対象にします。行を覆う ::after 擬似要素をリンクに設定し、名前だけを対象にしないようにします。
  • 行ごとに背景を付けず、ポインターに追従する単一のホバー強調表示を使います。
  • キーボード操作では、j/k または矢印キーで移動し、Enter で実行できるようにします。フォーカス中の行は枠線で明確に示します。
  • 説明文は省略しますが、名前は省略しません。
  • 行数が約 200 を超えたら仮想化します。

アイコン

セットは 1 つに統一します。線幅も 1 種類に統一します。用途ごとにサイズを 1 種類にします(高密度 UI では 14px、標準では 16px、タッチ操作では 20px)。テキストの色に追従するよう currentColor を使います。装飾用アイコンには aria-hidden を設定し、アイコンのみのボタンには aria-label を設定します。

テーブル

  • ヘッダー行は固定し、下罫線を付けます。
  • 数値は右揃えで等幅数字にし、テキストは左揃えにします。中央揃えにはしません。
  • 行の高さは 32~40px にします。縞模様は非常に高密度なテーブルでのみ、--hover の段階で使います。
  • 並べ替えインジケーターはヘッダーに表示し、ホバーしていなくてもアクティブな列が分かるようにします。
  • 空の状態はテーブル領域内に表示し、テーブル自体と置き換えないようにします。

コンポーネントのテスト

完了と判断する前に、実際に開いて次の操作を行います。

  1. マウスを外した状態で、Tab キーを使って操作します。
  2. 押して、長押しし、押したまま外へドラッグしてから離します。
  3. 幅を 375px に変更します。
  4. ダークモードとライトモードを切り替えます。
  5. 動きを減らす設定を有効にします。
  6. 現実的な最長のコンテンツと最短のコンテンツを入力します。
  7. スクリーンリーダーで、名前、ロール、状態が認識されるか確認します。

生成されたコンポーネントの兆候

兆候修正
useState と外側クリック用フックを使ったカスタムドロップダウンメニューのプリミティブを使う
8 種類の値を持つ variant4 種類にする
className が引き継がれていない引き継いでマージする
インラインの transition: all 0.3sトークンを使う
スピナーがラベルに置き換わり、ボタンが縮むスピナーを重ね、幅を固定する
ラベルのないアイコンのみのボタンaria-label を設定する
div onClickbutton を使う
フォーカストラップのないモーダルダイアログのプリミティブを使う

カタログから

コメント