Webパーツ辞典

複数選択プルダウン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>

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 と一覧の外のクリックで閉じ、フォーカスを開くボタンへ戻す

作るときに使えるツール

関連するパーツ