フローティングメニュー(FAB)
右下の+ボタンをタップすると複数アクションが縦に展開するスピードダイアル。
プレビュー
クリック
操作クリック/タップで動きます。
仕様書
- 向く場面
- モバイルで主操作+関連2〜3操作をまとめたい
- 画面右下に常駐させる作成・共有系の操作
- 画面を占有せず操作を隠しておきたい
- 避ける場面
- 操作が1つだけ(通常のFABでよい)
- アクションが5個以上で縦に伸びすぎる
- PCの広い画面でラベル無しアイコンだけを並べる
- 構成要素
- メインボタン
- アクションボタン
- ラベルツールチップ
- 展開領域
- 持っている状態
- 通常
- ホバー
- 押下中
- キーボード
Tab でメインボタンへ・Enter/Space で開閉。開くと Tab で各アクションへ移動(閉状態では入らない)・Esc で閉じてメインへ戻る。外側クリックで閉じる
- 支援技術
メインに aria-label と aria-expanded を同期、各アクション button に aria-label(ツールチップは ::after の装飾)。閉状態は visibility:hidden で Tab・読み上げから外す
- 動き
展開 max-height 0.35s、各アクションは opacity と translateY/scale 0.22s を 40ms ずつずらし、+→× を 0.3s で回転。reduced-motion では即時
原則フィッツの法則
デザインの要点
- アクションはメインに近いものから 40ms ずつ遅らせて出す。同時に出すと「1つの塊」に見え、閉じるときは順序を逆にして畳む
- +を 45° 回して×にする。閉じる操作が同じ場所にあることを形で示し、別の閉じるボタンを置かずに済む
- 閉じている間の展開領域は max-height:0 に visibility:hidden を重ねる。高さ 0 で隠しただけの button は Tab で届く
- ツールチップは ::after の装飾なので、各 button に aria-label を別に持たせる。data-label だけでは読み上げに名前が無い
コード(コピーして使えます)
<div class="fab-root">
<div class="fab-stage">
<p class="fab-hint">右下の + をタップ</p>
<div class="fab-wrap">
<div class="fab-actions">
<button class="fab-action" data-label="シェア" aria-label="シェア">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
</button>
<button class="fab-action" data-label="編集" aria-label="編集">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
</button>
<button class="fab-action" data-label="追加" aria-label="追加">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
</div>
<button class="fab-main" aria-label="メニューを開く" aria-expanded="false">
<svg class="fab-icon-plus" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<svg class="fab-icon-close" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
</div>
</div>