mcpField
B
button-states

button-states

github.com/dembrandtデザイン・UI174892 回インストール1 週間前に更新
リポジトリ

button-states について

ボタンなどのインタラクティブ要素に明確な視覚状態を定義するデザインスキルです。6つの状態、色の導出、キーボードフォーカス、無効状態、非同期処理中のフィードバックを扱います。

主な機能

  1. 016つのインタラクション状態
  2. 02状態色の算出指針
  3. 03視認できるキーボードフォーカス
  4. 04無効状態の動作
  5. 05読み込み中のフィードバックと再送防止

活用シーン

  1. 01デザインシステムでのボタン状態の定義
  2. 02キーボードフォーカスの視認性確認
  3. 03非同期処理のフィードバック設計

インストール

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

$npx skills add https://github.com/dembrandt/dembrandt-skills --skill button-states

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

詳細

ボタンとインタラクティブ要素の状態

すべてのインタラクティブコンポーネントには、各操作モードに対応する、視覚的に明確な状態が必要です。状態が欠けていたり曖昧だったりすると、UIが未完成に感じられ、ユーザーの信頼感が損なわれます。

6つの状態

状態トリガー視覚的な表示
通常デフォルト基本色、カーソル:pointer
ホバーマウスオーバーやや暗くなり、背景がわずかに変化
アクティブ/押下中マウスダウン/タップはっきり暗くなり、わずかに縮小
フォーカスキーボードナビゲーション視認できるフォーカスリング、塗りつぶし色は変化しない
無効使用不可コントラストが低く、カーソル:not-allowed、操作不可
読み込み中非同期処理の実行中スピナーまたはパルス表示、操作不可

アルゴリズムによる状態色の導出

状態色は個別に選ぶのではなく、HSLで明度を調整して基本色から導きます。これにより、パレット全体に一貫性が保たれます。

base:     hsl(H, S%, L%)
hover:    hsl(H, S%, L% - 8%)    ← darken 8%
active:   hsl(H, S%, L% - 14%)   ← darken 14%
例:プライマリボタン #635BFF(hsl 243, 100%, 68%)
.btn-primary {
  background: hsl(243, 100%, 68%);       /* rest    #635BFF */
}
.btn-primary:hover {
  background: hsl(243, 100%, 60%);       /* hover   #4A40FF */
}
.btn-primary:active {
  background: hsl(243, 100%, 54%);       /* active  #3429FF */
}

暗い背景に明るいボタンを配置する場合は、ロジックを逆にして、ホバー時に暗くするのではなく明るくします。

セカンダリ/アウトラインボタン
.btn-secondary {
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-text);
}
.btn-secondary:hover {
  background: var(--color-grey-100);     /* subtle fill */
  border-color: var(--color-grey-300);
}
.btn-secondary:active {
  background: var(--color-grey-200);
}

フォーカス状態

フォーカスはキーボードナビゲーションに必要です(WCAG 2.2)。視認できるようにし、ホバースタイルだけに頼ってはいけません。キーボードユーザーにはホバーが発生しないためです。

.btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}
  • フォーカスリングには box-shadow ではなく outline を使用します。最新のブラウザーでは outline が border-radius を尊重し、レイアウトにも影響しません
  • outline-offset: 2–4px にすると、リングとコンポーネントの端との間に余白ができます
  • 代わりのフォーカススタイルを用意せずに outline: none を使用してはいけません

無効状態

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}
  • 無効な要素はWCAGのコントラスト要件の対象外です。低い不透明度は適切であり、意図されたものです
  • JavaScriptを回避された場合でもクリックイベントを防ぐため、pointer-events: none を使用します
  • 無効なボタンの形状やサイズは変えないでください。変えるのは色とカーソルだけです

読み込み中の状態

ボタンが非同期処理を開始したら、ラベルをスピナーに置き換え、再送信を防ぎます。

.btn--loading {
  pointer-events: none;
  opacity: 0.7;
}

読み込み中もカーソルは変わりません。 カーソルが示すのは操作可能性であり、進行状況ではありません。通常のコンテンツには矢印、操作可能な要素には pointer を使います。進行状況は、マウスがたまたま置かれている場所ではなく、ユーザーがすでに見ている要素と読み込み領域で示します。また、cursor: wait は pointer-events: none と併用しても機能しないため、このルールは意図的なものとして読める一方、実際の動作には影響しません。

  • 読み込み中もボタンの幅を一定に保ちます。ラベルをスピナーに置き換える際にレイアウトがずれないようにします
  • 完了時(成功またはエラー)には通常状態に戻します
  • 処理に時間がかかる場合は、ステータスメッセージを併用します。スピナーだけではユーザーに何が起きているか伝わりません

アクティブ時の拡大縮小(任意)

押したときにわずかに縮小すると、ディズニーの「つぶし」の原則を取り入れた、物理的なフィードバックが加わります。

.btn:active {
  transform: scale(0.97);
  transition: transform 80ms ease-out;
}

スケール値は 0.95–0.98 の範囲にします。0.95 を下回ると、ボタンが壊れそうに感じられます。

ボタンCSSの完全なリファレンス

.btn {
  cursor: pointer;
  background: var(--color-primary);
  color: white;
  border-radius: var(--radius-button);
  padding: var(--component-padding-y-md) var(--component-padding-x-md);
  height: var(--component-height-md);
  border: none;
  transition: background 120ms ease-out, transform 80ms ease-out;
}

.btn:hover           { background: var(--color-primary-hover); }
.btn:active          { background: var(--color-primary-active); transform: scale(0.97); }
.btn:focus-visible   { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.btn:disabled        { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.btn.btn--loading    { opacity: 0.7; pointer-events: none; }

レビューチェックリスト

  • すべてのインタラクティブ要素に6つの状態が定義されていますか?
  • ホバー色とアクティブ色は、任意に選ぶのではなく、明度の調整によって基本色から導かれていますか?
  • フォーカス状態は視認でき、outline を使用していますか(削除されていませんか)?
  • 無効状態は不透明度が低く、cursor: not-allowed が設定されていますか?
  • 読み込み中の状態では再送信が防止されていますか?
  • トランジション時間は80~150msですか(瞬時でも遅すぎでもありませんか)?
  • 通常状態のすべてのインタラクティブ要素に cursor: pointer が設定されていますか?
  • 処理中にカーソルは変わらず、進行状況は要素自体で示されていますか?

コメント