ボタンやアバターなどのトリガー要素をクリックすると展開し、アクション・リンク・サブメニューを提供するUIコンポーネント。「三点リーダー(⋯)」メニュー・ユーザーアバターメニュー・ナビゲーションの「もっと見る」など、画面スペースを節約しながら複数のアクションを格納する用途で頻繁に使われる。
この記事を読むと、DropdownとSelect・Context Menuの使い分け・ARIA role="menu" パターン・フォーカス管理(開いたら最初の項目にフォーカス)・サブメニューとキーボードナビゲーションが自分でできるようになります。
1. UI例(Preview / Live)
実装で見る(GunjoUI)
この部品を、デザインシステム GUNJO の実装で確かめられます。
2. 定義(Definition)
トリガー要素(ボタン・アバター・三点リーダーなど)をクリックすると展開し、アクション・リンク・設定項目の一覧を表示するUIコンポーネント。「メニュー」として機能するため role="menu" と role="menuitem" の意味論的マークアップが必要。
SelectとDropdown Menuの違い:Selectは「フォームの入力値を選ぶ(値が変わる)」。Dropdown Menuは「アクションを実行する(コマンドの一覧)」。技術的には似た実装でも、意味論は異なる—Select は role="combobox" / role="listbox"、Dropdown Menu は role="menu" / role="menuitem" を使う。
Context MenuとDropdown Menuの違い:Context Menuは「右クリック(長押し)で表示するコンテキスト依存のメニュー」。Dropdown MenuはUI上のトリガーをクリックして明示的に開くメニュー。
3. 使い分け(When to use / When NOT to use)
3.1 When to use
- 1つのトリガーから複数のアクションを提供したい場合(編集・複製・削除など)
- 画面スペースを節約してアクションを折りたたむ必要がある場合(三点リーダーメニュー)
- ユーザーアカウントメニュー(アバタークリックでプロフィール・設定・ログアウト)
- ナビゲーションバーの「もっと見る」メニュー
3.2 When NOT to use
- フォームで値を選ぶ →
Select(またはCombobox)を使う - 操作が1つだけ → 直接ボタンを置く
- 全メニュー項目を常時表示できるスペースがある → 常時表示のほうが発見しやすい
3.3 代替UI(Alternatives)
- フォームの選択肢 →
Select - コンテンツの右クリックメニュー →
Context Menu - 複雑なアクションの検索・実行 →
Command Palette - 簡単な確認ダイアログ →
Popover
4. 設計判断の核(Decision Principles)
Dropdown Menuの核はフォーカス管理——開いたら最初の項目にフォーカスを移し、Escで閉じてトリガーにフォーカスを戻す。この動作がないとキーボードユーザーはメニューに閉じ込められる。
判断の優先順位:① フォーカス管理 → ② 開閉ARIAの付与 → ③ 項目のグループ化 → ④ 破壊的アクションの分離
- 開いたら最初のメニュー項目にフォーカスを移す:
role="menu"を持つ要素が開いたら、最初のrole="menuitem"に自動的にフォーカスする。そうしないとキーボードユーザーはメニューの存在に気づかない - Escで閉じてトリガーにフォーカスを戻す:Escキーでメニューを閉じた後、
⌘Kのように開いたボタンにフォーカスを戻す。これがないと次のTabキー操作で意図しない場所にジャンプする - 破壊的アクションはSeparatorで区切り赤色で示す:「削除する」などの取り消し不能な操作はメニュー下部にSeparatorで分離し、赤色テキストで示す。ユーザーが誤って実行するリスクを下げる
- 三点リーダーボタンには
aria-labelが必須:アイコンのみのトリガーボタン(⋯・⋮)は、スクリーンリーダーが何のボタンか読めない。aria-label="記事のオプション"のように具体的に説明する
5. 状態設計(States)
5.1 必須状態(Required)
- Closed(閉じた状態):トリガーボタンのみ表示
- Open(展開した状態):メニューが表示され、最初の項目にフォーカス
5.2 条件付き状態(Conditional)
- Hover(項目にカーソル):ハイライト表示
- Focus(キーボードでの選択中):フォーカスリング表示
- Disabled(無効化された項目):グレーアウト、
aria-disabled="true" - Checked(チェック可能な項目):
role="menuitemcheckbox"+ チェックアイコン
5.3 State Gallery
| 状態 | 必須 | 何を伝えるか |
|---|---|---|
| Closed | ✅ | 展開可能(aria-haspopup で伝える) |
| Open | ✅ | メニュー項目の一覧 |
| Item Hover / Focus | ✅ | 選択可能・選択中 |
| Item Disabled | — | この操作は現在使えない |
| Item Checked | — | この設定がオンになっている |
6. バリエーション設計(Variants)
用途によってメニューの内容構成が変わる。
| バリアント | 内容 | 典型的なトリガー |
|---|---|---|
| アクションメニュー | 編集・複製・削除などのアクション | 三点リーダー(⋯) |
| ユーザーメニュー | プロフィール・設定・ログアウト | アバター |
| ナビゲーションメニュー | ページリンクの一覧 | 「もっと見る」ボタン |
| 設定メニュー | トグル可能な設定項目 | 設定アイコン |
禁止パターン:フォームの <select> の代替として Dropdown Menu を使う → <select> が意味論的に正しく、フォーム送信・モバイルの標準UIとの統合が壊れる。フォームにはSelectコンポーネントを使う。
7. パターン集(Good / Bad / How to fix)
7.0 よく崩れる設計パターン(3つ)
- フォーカス管理なし:メニューが開いてもフォーカスが移らず、キーボードユーザーがメニュー項目にたどり着けない
- Escで閉じた後のフォーカス喪失:Escで閉じた後フォーカスが行方不明になり、次のTabキーで意図しない場所にジャンプする
- 三点リーダーボタンに aria-label がない:スクリーンリーダーが「ボタン」としか読み上げず、何のメニューか分からない
7.1 Bad(典型3つ)
- メニューが開いてもフォーカスが入力フィールドに残ったまま、↓キーを押しても何も選択されない
- Escキーのイベントを実装しておらず、メニューを閉じるにはEsc以外の手段(Tabで抜けるなど)が必要
<button>⋯</button>だけでラベルなし、スクリーンリーダーが「ボタン」としか読み上げない
7.2 Good(対になる3つ)
- メニューが開いたら
useEffectで最初の[role="menuitem"]にfocus()を呼ぶ onKeyDownにEscapeを実装し、メニューを閉じた後triggerRef.current?.focus()でトリガーにフォーカスを戻す<button aria-label="記事『Button』のオプション">⋯</button>のようにコンテキストを含めたaria-labelを付与する
7.3 How to fix(手順)
useRefでトリガーボタンの参照を保持し、onClose時にtriggerRef.current?.focus()を呼ぶ- メニューが開いた時(
open === true)のuseEffectでmenuRef.current?.querySelector('[role="menuitem"]')?.focus()を呼ぶ onKeyDownに↑・↓(項目移動)・Enter(実行)・Escape(閉じる)・Home(最初に移動)・End(最後に移動)を実装する- 三点リーダーなどアイコンのみのボタンに
aria-labelを追加する
7.4 GUNJO 実装で見る(Bad / Good)
同じ GUNJO の DropdownMenu を、崩れた使い方と正しい使い方で対比。
8. ルール(Must / Better)
Must(守らないと壊れる)
Better(品質が跳ねる)
9. アクセシビリティ要件(必須)
Keyboard
Enter/Spaceでトリガーボタンをクリックしてメニューを開く↓/↑でメニュー項目を移動(最後の項目の↓で最初に戻るループも推奨)Enterで選択中の項目を実行してメニューを閉じるEscapeでメニューを閉じてトリガーにフォーカスを戻すHome/Endでリストの最初・最後に移動する
Focus
- メニューが開いたら最初の
menuitemにフォーカスを移す - 閉じた後はトリガーボタンにフォーカスを戻す
- メニュー外へのTabフォーカスを許容(フォーカストラップは不要、Tabで自然にメニューを閉じるのも可)
Screen Reader
<!-- トリガーボタン -->
<button
aria-haspopup="menu"
aria-expanded="true"
aria-controls="user-menu"
>
ユーザーメニューを開く
</button>
<!-- メニュー -->
<div id="user-menu" role="menu" aria-label="ユーザーメニュー">
<a role="menuitem" href="/profile">プロフィール</a>
<a role="menuitem" href="/settings">設定</a>
<div role="separator" aria-hidden="true"></div>
<button role="menuitem">ログアウト</button>
</div>
Touch / Pointer
- メニュー項目のタップ領域は最低44pxの高さを確保する
- メニュー外タップでメニューを閉じる(
touchstartまたはmousedownで実装)
Contrast / Readability
- メニュー項目のホバー・フォーカス背景のコントラストを確保する
- 破壊的アクションの赤色テキストと背景のコントラストは4.5:1以上
10. 実装メモ(Implementation Notes)
- shadcn/ui の
DropdownMenuは@radix-ui/react-dropdown-menuベースで、role="menu"・キーボードナビゲーション・フォーカス管理・aria-haspopup・Floating UI による位置自動調整がすべて実装済み - メニューの位置計算(画面端での自動反転)は Floating UI(
@floating-ui/react)を使うのが最善。placement="bottom-start"で展開方向を指定し、画面端では自動で反転する aria-disabled="true"+pointer-events: noneはdisabled属性と異なりフォーカス可能なため、スクリーンリーダーが無効状態を伝えやすい。スクリーンリーダーユーザーのために無効な理由をTooltipで示すことも検討する- 三点リーダーボタンの
aria-labelは「オプション」だけでなく「記事『Button』のオプション」のようにコンテキストを含めると、複数の三点リーダーが並ぶ画面で識別しやすい
11. 関連リンク
- 関連するUIデザイン原則: 視覚的階層 (Visual Hierarchy), フィードバック (Feedback), 段階的開示 (Progressive Disclosure)
- 用語集(定義): 視覚的階層 (Visual Hierarchy)
- 関連するUIコンポーネント(横): Command Palette(コマンドパレット), Select(セレクト), Popover(ポップオーバー), Separator(セパレーター)
12. まとめ
Dropdown Menuの設計はフォーカス管理が最重要です。迷ったら 4. 設計判断の核 に戻り、「開いたら最初の項目にフォーカス」「Escで閉じてトリガーに戻す」「三点リーダーには aria-label」の3点を確認してください。shadcn/ui の DropdownMenu を使えばこれらは自動的に解決されます。Selectとの混同に注意——アクションのリストには role="menu"、フォームの値選択には role="listbox" を使い分けることが、意味論的に正しいUI設計の基本です。