表示例
- 名前を変える
- 複製する
- 削除する
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- プルダウンメニュー
- 展開メニュー
- メニュー
- ドロップダウン
- メニューリストボックス
- Dropdown menu
実装例
<div class="x-dropdown">
<button class="x-dropdown__b" type="button" aria-expanded="true" aria-haspopup="true" aria-controls="x-dropdown-m">
操作
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
<!-- 見本なので開いた状態で流れの中に置いている。実際は ul に popover 属性、button に popovertarget="x-dropdown-m" を付けて開閉し、
aria-expanded は false から始めて toggle イベントで書き換える(hidden 属性では閉じない) -->
<ul class="x-dropdown__m" id="x-dropdown-m" role="menu">
<li role="menuitem" tabindex="-1">名前を変える</li>
<li role="menuitem" tabindex="-1">複製する</li>
<li role="menuitem" tabindex="-1" class="x-dropdown__danger">削除する</li>
</ul>
</div>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-dropdown,
.x-dropdown *:not([class*="fa-"]),
.x-dropdown *:not([class*="fa-"])::before,
.x-dropdown *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-dropdown {
width: 160px;
}
.x-dropdown__b {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: 36px;
padding: 0 12px;
border: 1px solid #cfd4dc;
border-radius: 8px;
background: #fff;
font: inherit;
font-size: 12px;
cursor: pointer;
}
.x-dropdown__m {
margin: 6px 0 0;
padding: 5px;
border: 1px solid #e3e6ea;
border-radius: 8px;
background: #fff;
list-style: none;
}
.x-dropdown__m li {
padding: 6px 8px;
border-radius: 6px;
font-size: 11.5px;
cursor: pointer;
}
.x-dropdown__m li:hover {
background: #f1f3f6;
}
.x-dropdown__danger {
color: #b91c1c;
}
AIへの指示文
「ドロップダウンメニュー(Dropdown menu)」を実装してください。
## これは何か
押すと下に選択肢が開くメニュー。入力欄ではなく、操作を選ばせる点がセレクトボックスと違う。
## 使いどころ
- 「編集」「複製」「削除」のように、その場で実行する操作をまとめるとき
- ヘッダーで、下の階層のページを一段見せたいとき
## 実装の要件
- button[type=button][popovertarget="id"][aria-expanded][aria-controls="id"] + ul[popover][id] の構成にする。項目を選んだら hidePopover() で閉じる(項目の click は外側クリックではないので、書かないと開いたまま残る)
- メニューに popover 属性を付け、button の popovertarget で開閉する(閉じた状態を hidden 属性で持たない)。外側クリックと Esc で閉じる動きはブラウザが持つ(Chrome・Edge は 114 から、Safari は 17 から、Firefox は 125 から。iOS の Safari が外側のタップで閉じるのは 18.3 から)
- aria-expanded はメニューの toggle イベントの newState(open / closed)に合わせて true / false に書き換え、外側クリックや Esc で閉じたときも古い値を残さない
- 開いたら中の項目へフォーカスを移すので、開くボタンの click で先に this.focus() を呼ぶ。Safari と macOS の Firefox はマウスで押したボタンにフォーカスを移さず、そのままだと Esc で閉じたときにボタンへ戻らない
- popover は最前面の層に出て親の position: relative が効かない。位置は見本の margin も含めて @supports (position-area: block-end) の中だけに書き、押したボタンが暗黙のアンカーになるのを使う(Chrome・Edge は 133 から、Safari は 26 から、Firefox は 147 から)
- @supports の中では margin: 6px 0 0; inset: auto; width: max-content; min-width: anchor-size(width); position-area: block-end span-inline-end; を指定し、ボタンの下に左端をそろえて、ボタン以上の幅で開く
- 同じ @supports の中で position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; を付け、下端では上へ、右端ではボタンの右端にそろえて左へ回す。width: max-content が無いと、右端で回らずに細く縮む
- position-area に対応しないブラウザ向けに、@supports not (position-area: block-end) の中でメニューに margin: auto; を書き、画面の中央に出す(popover の既定の margin: auto に頼ると、margin を 0 にするリセット CSS に消されて画面の端に張りつく)
- 値を選ばせたいなら select を使う。操作を選ばせるのがドロップダウン
- ホバーだけで開くメニューは、タッチ環境で扱えない
- 入れ子のメニューは、マウスを滑らせると閉じてしまう。1段に留める
## アクセシビリティの要件
- 開く要素は button にし、aria-expanded を付ける。中身が role="menu" のときだけ aria-haspopup="true" も付け、リンク集では付けない
- 中身は role="menu" / role="menuitem"(本当にメニューのときだけ。リンク集には使わない)
- 矢印キーで項目を移動でき、Esc で閉じ、開いたボタンへ戻る
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<div class="x-dropdown">
<button class="x-dropdown__b" type="button" aria-expanded="true" aria-haspopup="true" aria-controls="x-dropdown-m">
操作
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
<!-- 見本なので開いた状態で流れの中に置いている。実際は ul に popover 属性、button に popovertarget="x-dropdown-m" を付けて開閉し、
aria-expanded は false から始めて toggle イベントで書き換える(hidden 属性では閉じない) -->
<ul class="x-dropdown__m" id="x-dropdown-m" role="menu">
<li role="menuitem" tabindex="-1">名前を変える</li>
<li role="menuitem" tabindex="-1">複製する</li>
<li role="menuitem" tabindex="-1" class="x-dropdown__danger">削除する</li>
</ul>
</div>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-dropdown,
.x-dropdown *:not([class*="fa-"]),
.x-dropdown *:not([class*="fa-"])::before,
.x-dropdown *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-dropdown {
width: 160px;
}
.x-dropdown__b {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: 36px;
padding: 0 12px;
border: 1px solid #cfd4dc;
border-radius: 8px;
background: #fff;
font: inherit;
font-size: 12px;
cursor: pointer;
}
.x-dropdown__m {
margin: 6px 0 0;
padding: 5px;
border: 1px solid #e3e6ea;
border-radius: 8px;
background: #fff;
list-style: none;
}
.x-dropdown__m li {
padding: 6px 8px;
border-radius: 6px;
font-size: 11.5px;
cursor: pointer;
}
.x-dropdown__m li:hover {
background: #f1f3f6;
}
.x-dropdown__danger {
color: #b91c1c;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- 「編集」「複製」「削除」のように、その場で実行する操作をまとめるとき
- ヘッダーで、下の階層のページを一段見せたいとき
注意点
- 値を選ばせたいなら select を使う。操作を選ばせるのがドロップダウン
- ホバーだけで開くメニューは、タッチ環境で扱えない
- 入れ子のメニューは、マウスを滑らせると閉じてしまう。1段に留める
アクセシビリティの要点
- 開く要素は button にし、aria-expanded を付ける。中身が role="menu" のときだけ aria-haspopup="true" も付け、リンク集では付けない
- 中身は role="menu" / role="menuitem"(本当にメニューのときだけ。リンク集には使わない)
- 矢印キーで項目を移動でき、Esc で閉じ、開いたボタンへ戻る