複数選択プルダウンMulti-select
見た目はプルダウンだが、開くとチェックボックスつきの一覧が出て、いくつでも選べる。閉じると「3件選択」のように件数や選んだ名前がボタンに出る。
表示例
対応エリア
- 東京都
- 神奈川県
- 千葉県
- 埼玉県
- 茨城県
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- マルチセレクト
- 複数選択ドロップダウン
- チェックボックス付きプルダウン
- 複数選択できるセレクトボックス
- Multi-select
実装例
<div class="x-multi-select">
<span class="x-multi-select__l" id="x-multi-select-l">対応エリア</span>
<button class="x-multi-select__t" type="button" aria-haspopup="listbox" aria-expanded="true"
aria-controls="x-multi-select-lb" aria-labelledby="x-multi-select-l x-multi-select-v">
<span class="x-multi-select__v" id="x-multi-select-v">3件選択</span>
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
<!-- 見本なので開いた状態。開いたら一覧へフォーカスを移し、Esc と一覧の外のクリックで閉じてボタンへ戻す -->
<div class="x-multi-select__pop">
<!-- 上下の矢印キーで動かし、Space で選択を切り替える。いま指している項目を aria-activedescendant で示す -->
<ul class="x-multi-select__lb" id="x-multi-select-lb" role="listbox" aria-multiselectable="true"
aria-labelledby="x-multi-select-l" tabindex="0" aria-activedescendant="x-multi-select-o2">
<li class="x-multi-select__o" id="x-multi-select-o1" role="option" aria-selected="true"><span class="x-multi-select__box" aria-hidden="true"></span>東京都</li>
<li class="x-multi-select__o x-multi-select__o--cur" id="x-multi-select-o2" role="option" aria-selected="true"><span class="x-multi-select__box" aria-hidden="true"></span>神奈川県</li>
<li class="x-multi-select__o" id="x-multi-select-o3" role="option" aria-selected="false"><span class="x-multi-select__box" aria-hidden="true"></span>千葉県</li>
<li class="x-multi-select__o" id="x-multi-select-o4" role="option" aria-selected="true"><span class="x-multi-select__box" aria-hidden="true"></span>埼玉県</li>
<li class="x-multi-select__o" id="x-multi-select-o5" role="option" aria-selected="false"><span class="x-multi-select__box" aria-hidden="true"></span>茨城県</li>
</ul>
<div class="x-multi-select__foot">
<button class="x-multi-select__clr" type="button">選択をすべて解除</button>
</div>
</div>
</div>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-multi-select,
.x-multi-select *:not([class*="fa-"]),
.x-multi-select *:not([class*="fa-"])::before,
.x-multi-select *: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-multi-select {
width: 260px;
max-width: 100%;
color: #1b2028;
font-size: 14px;
}
.x-multi-select__l {
display: block;
margin-bottom: 6px;
font-size: 12px;
font-weight: 700;
}
.x-multi-select__t {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 40px;
padding: 0 12px;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
color: #1b2028;
font: inherit;
font-size: 14px;
text-align: left;
cursor: pointer;
}
.x-multi-select__t[aria-expanded="true"] .fa-chevron-down {
transform: rotate(180deg);
}
/* 実際のページでは position: absolute でボタンの下に重ねる。見本は流れの中に置いている */
.x-multi-select__pop {
margin-top: 4px;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
box-shadow: 0 6px 16px rgba(27, 32, 40, .12);
overflow: hidden;
}
.x-multi-select__lb {
max-height: 128px;
margin: 0;
padding: 4px;
overflow-y: auto;
list-style: none;
}
.x-multi-select__lb:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
.x-multi-select__o {
display: flex;
align-items: center;
gap: 8px;
height: 30px;
padding: 0 8px;
border-radius: 6px;
cursor: pointer;
}
.x-multi-select__o:hover {
background: #f4f6f9;
}
/* 矢印キーで指している項目。選択の有無とは別に、枠で示す */
.x-multi-select__o--cur {
box-shadow: inset 0 0 0 2px #1d4ed8;
}
.x-multi-select__box {
position: relative;
width: 16px;
height: 16px;
flex: none;
border: 1.5px solid #5b6472;
border-radius: 3px;
background: #fff;
}
/* 選んだ項目はチェックの形で示す。色だけに頼らない */
.x-multi-select__o[aria-selected="true"] .x-multi-select__box {
border-color: #1d4ed8;
background: #1d4ed8;
}
.x-multi-select__o[aria-selected="true"] .x-multi-select__box::after {
content: "";
position: absolute;
top: 1px;
left: 4.5px;
width: 4px;
height: 8px;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.x-multi-select__foot {
padding: 4px 8px;
border-top: 1px solid #d5dae1;
text-align: right;
}
.x-multi-select__clr {
height: 26px;
padding: 0 8px;
border: 0;
border-radius: 6px;
background: none;
color: #1d4ed8;
font: inherit;
font-size: 12px;
text-decoration: underline;
cursor: pointer;
}
.x-multi-select__t:focus-visible,
.x-multi-select__clr:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 1px;
}
AIへの指示文
「複数選択プルダウン(Multi-select)」を実装してください。
## これは何か
見た目はプルダウンだが、開くとチェックボックスつきの一覧が出て、いくつでも選べる。閉じると「3件選択」のように件数や選んだ名前がボタンに出る。
## 使いどころ
- 対応エリア・絞り込みの条件のように、候補が10件を超え、いくつでも選べる項目を狭い場所に収めたいとき
- 一覧の上の絞り込みのように、ふだんは閉じておき、必要なときだけ開いて選び直してもらうとき
## 実装の要件
- ラベル + button[aria-haspopup=listbox][aria-expanded][aria-controls] + 一覧のパネル > ul[role=listbox][aria-multiselectable=true][aria-activedescendant] > li[role=option][aria-selected]
- パネルはボタンの真下に position: absolute で重ねる。下端に「選択をすべて解除」、上端に絞り込みの欄を置いてもよい
- 絞り込みの欄の keydown は、event.isComposing が true か event.keyCode が 229 のとき何もしない。変換中の Space と↑↓は日本語入力が使う
- チェックボックスは本物の input を使わず、aria-selected に合わせて CSS で描く(option の中に操作できる要素を入れない)
- 候補が数件ならチェックボックスを並べたほうが速い。開かないと選択肢が見えない分、1手間増える
- 1つだけ選ぶならセレクトボックス、自由な語を足してもらうならタグ入力。決まった候補から複数を選ぶのがこの部品
- 閉じたときの表示を決める。「3件選択」だけでは中身が分からないので、2件までは名前、それを超えたら「東京都 ほか2件」のようにする
- select に multiple を付けた標準の一覧は、パソコンでは Ctrl や ⌘ を押しながらでないと複数を選べず、知らない人が多い。そのまま使わない
- 候補が多いなら一覧の上に絞り込みの欄を置く。選んだ項目は絞り込みで隠れても選ばれたままにする
## アクセシビリティの要件
- 開くボタンは aria-haspopup="listbox" と aria-expanded を持ち、aria-labelledby で項目名と「3件選択」の両方を名前にする
- 一覧は role="listbox" に aria-multiselectable="true" を付け、各項目を role="option" と aria-selected にする。上下の矢印キーで動かし、Space で選択を切り替える(WAI-ARIA APG の Listbox)
- いま指している項目は aria-activedescendant で示し、選んだかどうかとは別の見た目(枠)で出す
- 選んだ項目はチェックの形でも示す(達成基準 1.4.1 色の用途)
- Esc と一覧の外のクリックで閉じ、フォーカスを開くボタンへ戻す
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<div class="x-multi-select">
<span class="x-multi-select__l" id="x-multi-select-l">対応エリア</span>
<button class="x-multi-select__t" type="button" aria-haspopup="listbox" aria-expanded="true"
aria-controls="x-multi-select-lb" aria-labelledby="x-multi-select-l x-multi-select-v">
<span class="x-multi-select__v" id="x-multi-select-v">3件選択</span>
<i class="fa-solid fa-chevron-down" aria-hidden="true"></i>
</button>
<!-- 見本なので開いた状態。開いたら一覧へフォーカスを移し、Esc と一覧の外のクリックで閉じてボタンへ戻す -->
<div class="x-multi-select__pop">
<!-- 上下の矢印キーで動かし、Space で選択を切り替える。いま指している項目を aria-activedescendant で示す -->
<ul class="x-multi-select__lb" id="x-multi-select-lb" role="listbox" aria-multiselectable="true"
aria-labelledby="x-multi-select-l" tabindex="0" aria-activedescendant="x-multi-select-o2">
<li class="x-multi-select__o" id="x-multi-select-o1" role="option" aria-selected="true"><span class="x-multi-select__box" aria-hidden="true"></span>東京都</li>
<li class="x-multi-select__o x-multi-select__o--cur" id="x-multi-select-o2" role="option" aria-selected="true"><span class="x-multi-select__box" aria-hidden="true"></span>神奈川県</li>
<li class="x-multi-select__o" id="x-multi-select-o3" role="option" aria-selected="false"><span class="x-multi-select__box" aria-hidden="true"></span>千葉県</li>
<li class="x-multi-select__o" id="x-multi-select-o4" role="option" aria-selected="true"><span class="x-multi-select__box" aria-hidden="true"></span>埼玉県</li>
<li class="x-multi-select__o" id="x-multi-select-o5" role="option" aria-selected="false"><span class="x-multi-select__box" aria-hidden="true"></span>茨城県</li>
</ul>
<div class="x-multi-select__foot">
<button class="x-multi-select__clr" type="button">選択をすべて解除</button>
</div>
</div>
</div>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-multi-select,
.x-multi-select *:not([class*="fa-"]),
.x-multi-select *:not([class*="fa-"])::before,
.x-multi-select *: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-multi-select {
width: 260px;
max-width: 100%;
color: #1b2028;
font-size: 14px;
}
.x-multi-select__l {
display: block;
margin-bottom: 6px;
font-size: 12px;
font-weight: 700;
}
.x-multi-select__t {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 40px;
padding: 0 12px;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
color: #1b2028;
font: inherit;
font-size: 14px;
text-align: left;
cursor: pointer;
}
.x-multi-select__t[aria-expanded="true"] .fa-chevron-down {
transform: rotate(180deg);
}
/* 実際のページでは position: absolute でボタンの下に重ねる。見本は流れの中に置いている */
.x-multi-select__pop {
margin-top: 4px;
border: 1px solid #7f8a99;
border-radius: 8px;
background: #fff;
box-shadow: 0 6px 16px rgba(27, 32, 40, .12);
overflow: hidden;
}
.x-multi-select__lb {
max-height: 128px;
margin: 0;
padding: 4px;
overflow-y: auto;
list-style: none;
}
.x-multi-select__lb:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: -2px;
}
.x-multi-select__o {
display: flex;
align-items: center;
gap: 8px;
height: 30px;
padding: 0 8px;
border-radius: 6px;
cursor: pointer;
}
.x-multi-select__o:hover {
background: #f4f6f9;
}
/* 矢印キーで指している項目。選択の有無とは別に、枠で示す */
.x-multi-select__o--cur {
box-shadow: inset 0 0 0 2px #1d4ed8;
}
.x-multi-select__box {
position: relative;
width: 16px;
height: 16px;
flex: none;
border: 1.5px solid #5b6472;
border-radius: 3px;
background: #fff;
}
/* 選んだ項目はチェックの形で示す。色だけに頼らない */
.x-multi-select__o[aria-selected="true"] .x-multi-select__box {
border-color: #1d4ed8;
background: #1d4ed8;
}
.x-multi-select__o[aria-selected="true"] .x-multi-select__box::after {
content: "";
position: absolute;
top: 1px;
left: 4.5px;
width: 4px;
height: 8px;
border: solid #fff;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.x-multi-select__foot {
padding: 4px 8px;
border-top: 1px solid #d5dae1;
text-align: right;
}
.x-multi-select__clr {
height: 26px;
padding: 0 8px;
border: 0;
border-radius: 6px;
background: none;
color: #1d4ed8;
font: inherit;
font-size: 12px;
text-decoration: underline;
cursor: pointer;
}
.x-multi-select__t:focus-visible,
.x-multi-select__clr:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 1px;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- 対応エリア・絞り込みの条件のように、候補が10件を超え、いくつでも選べる項目を狭い場所に収めたいとき
- 一覧の上の絞り込みのように、ふだんは閉じておき、必要なときだけ開いて選び直してもらうとき
注意点
- 候補が数件ならチェックボックスを並べたほうが速い。開かないと選択肢が見えない分、1手間増える
- 1つだけ選ぶならセレクトボックス、自由な語を足してもらうならタグ入力。決まった候補から複数を選ぶのがこの部品
- 閉じたときの表示を決める。「3件選択」だけでは中身が分からないので、2件までは名前、それを超えたら「東京都 ほか2件」のようにする
- select に multiple を付けた標準の一覧は、パソコンでは Ctrl や ⌘ を押しながらでないと複数を選べず、知らない人が多い。そのまま使わない
- 候補が多いなら一覧の上に絞り込みの欄を置く。選んだ項目は絞り込みで隠れても選ばれたままにする
アクセシビリティの要点
- 開くボタンは aria-haspopup="listbox" と aria-expanded を持ち、aria-labelledby で項目名と「3件選択」の両方を名前にする
- 一覧は role="listbox" に aria-multiselectable="true" を付け、各項目を role="option" と aria-selected にする。上下の矢印キーで動かし、Space で選択を切り替える(WAI-ARIA APG の Listbox)
- いま指している項目は aria-activedescendant で示し、選んだかどうかとは別の見た目(枠)で出す
- 選んだ項目はチェックの形でも示す(達成基準 1.4.1 色の用途)
- Esc と一覧の外のクリックで閉じ、フォーカスを開くボタンへ戻す