コンテキストメニュー
ステージ上を右クリック(長押し)すると、カーソル位置にメニューが開く。アイコン付き項目・区切り線・破壊的項目を持ち、項目クリック・外側クリック・Escで閉じる。
プレビュー
クリック
操作クリック/タップで動きます。
仕様書
- 向く場面
- 右クリックで対象に紐づく操作を出す
- 行・カードごとの編集/複製/削除
- スマホの長押しメニュー
- 避ける場面
- 主要な操作の唯一の入口(発見できない)
- 項目が多い階層メニュー
- 右クリックを別用途で使う画面
- 構成要素
- ステージ
- メニュー
- 項目(アイコン付き)
- 区切り線
- 破壊的項目
- 結果表示
- 持っている状態
- 通常
- ホバー
- フォーカス
- キーボード
Tab でステージへ、Shift+F10/ContextMenu キーで開くと先頭項目へ。↑↓で巡回、Home/End で端、Enter/Space で実行、Esc で閉じてステージへ戻る。マウスは右クリック、タッチは長押し 500ms
- 支援技術
role=menu/menuitem、閉じている間は inert+aria-hidden。開いたら先頭項目へフォーカス。破壊的項目は色に加え区切り線の下に置く。選択結果は role=status で通知。reduce で停止
- 動き
開閉 140ms ease で opacity・scale 0.94→1(origin 左上)。カーソル位置から生えて見せる
デザインの要点
- メニューは押した座標から生やし、はみ出す分だけ内側へ寄せる。画面端で右クリックしても項目が切れない
- 開いたら先頭項目へフォーカスを移し、↑↓で巡回・Esc で戻す。マウス専用にすると右クリックできない人が締め出される
- transform-origin を左上にして 0.94→1 に開く。カーソルの位置から「生えた」ように見え、どこを押したかが残る
- 「削除する」は区切り線の下に離して朱色にする。隣接させると「名前を変更」の指が削除に届く
実装メモ
プレビューは枠内に収めるため、メニューを position:absolute(ステージ基準)で実装し、座標もステージ内へクランプしています。実際のページで最前面に出すときは position:fixed に変え、body 直下など高い階層へ置き、window の幅・高さでクランプしてください。ブラウザ標準の右クリックメニューは e.preventDefault() で抑止します。キーボードではステージにフォーカスして Shift+F10(または ContextMenu キー)で同じメニューが中央に開きます。
コード(コピーして使えます)
<div class="stage" id="stage" tabindex="0" aria-label="右クリック(キーボードは Shift+F10)でメニューを開く領域">
<p class="hint" id="hint">ここを<b>右クリック</b><br>(スマホは長押し・キーボードは Shift+F10)</p>
<div class="ctx-menu" id="ctxMenu" role="menu" aria-hidden="true" inert>
<button class="ctx-item" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path d="M2 11.5h10M9 2.5l2.5 2.5-6 6-2.8.5.5-2.8 6-6z" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
編集する
</button>
<button class="ctx-item" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<rect x="2.5" y="4.5" width="7.5" height="7.5" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
<path d="M4.5 4.5V3a1.5 1.5 0 011.5-1.5h4A1.5 1.5 0 0111.5 3v4a1.5 1.5 0 01-1.5 1.5" stroke="currentColor" stroke-width="1.4"/>
</svg>
複製する
</button>
<button class="ctx-item" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path d="M7 2v7M4 6l3 3 3-3M2.5 11.5h9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
名前を変更
</button>
<hr class="ctx-divider" aria-hidden="true"/>
<button class="ctx-item ctx-item--danger" role="menuitem">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path d="M2.5 3.5h9M5 3.5V2.5a.5.5 0 01.5-.5h3a.5.5 0 01.5.5v1M4.5 3.5l.5 8M9.5 3.5l-.5 8M3.5 3.5l.5 8h6l.5-8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
削除する
</button>
</div>
<p class="ctx-result" id="ctxResult" role="status" aria-live="polite"></p>
</div>