Molecules
Combobox
A text field that filters a list of options, for single select or multi-select with Chips.
- Epimedium
- Brunnera
- Pulmonaria
- Hellebore
- Heuchera
Code
<div class="field">
<label for="default-plant" class="field__label label">Plant</label>
<div class="combobox">
<div class="combobox__control">
<input id="default-plant" class="combobox__input" role="combobox" aria-expanded="false"
aria-controls="default-plant-list" aria-autocomplete="list" autocomplete="off" />
<button type="button" class="button button--plain button--icon combobox__toggle" tabindex="-1" aria-label="Show plants">
<svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#ChevronDown"/></svg>
</button>
</div>
<ul id="default-plant-list" class="combobox__listbox" role="listbox" popover="manual" aria-label="Plants">
<li id="default-plant-1" class="combobox__option" role="option" aria-selected="false">
<span>Epimedium</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="default-plant-2" class="combobox__option" role="option" aria-selected="false">
<span>Brunnera</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="default-plant-3" class="combobox__option" role="option" aria-selected="false">
<span>Pulmonaria</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="default-plant-4" class="combobox__option" role="option" aria-selected="false">
<span>Hellebore</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="default-plant-5" class="combobox__option" role="option" aria-selected="false">
<span>Heuchera</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
</ul>
<p class="sr-only" role="status"></p>
</div>
</div>
Why it exists
A Combobox is the last thing to reach for. A native <select> or <datalist> comes with keyboard use, screen reader support and a phone’s own picker, and a Combobox has to script all of that. So this page starts by asking whether you need one. You do when a list is too long to scroll, or when people pick several values.
The library draws it and leaves the behaviour to you. The field shares its base with the other text controls, so it matches an Input beside it. The list is a manual popover, so a Card or Dialog can’t clip it. What happens on each key depends on your data and where it comes from, so the script is the product’s, and the steps it must take are set out below.
Focus stays in the input the whole time. Someone can keep typing to narrow the list, while aria-activedescendant tells a screen reader which option is active.
When to use
Choose the simplest control that works.
- Pick one from a short, known list:
.select--custom(native, no script). - Suggestions while typing, any value allowed:
<input list>with a<datalist>(native, unstyled list). - Filter a long list, multi-select with Chips, rich options:
.combobox, with the product’s script. - Not without a script: the library ships none, and the list needs the steps in the product’s script. The examples on this page run a demo of those steps.
Rules
- Name the field with a
<label>; name the list witharia-label. - Don’t put links or Buttons inside options.
- Buttons in the field are
.button.button--plain.button--icon, sized to fit the field with a 48px hit area.tabindex="-1": keyboard users use the arrow keys and Escape..combobox__toggleturns its chevron while the list is open. - The status element (
role="status", visually hidden) carries the result count, so it is announced.
Keyboard and screen readers
The browser provides focus, typing and Tab. Everything to do with the list is the product’s script.
- Typing filters the list and opens it.
- ArrowDown opens the list and moves to the next option, ArrowUp to the previous. Focus stays in the input.
- Enter selects the active option.
- Escape closes an open list. With the list closed, it clears the input.
- Backspace in an empty multi-select input removes the last Chip.
- Tab leaves the field and closes the list. A Chip’s remove Button is in the tab order; the field’s other Buttons aren’t.
- A screen reader hears a combobox from
role="combobox", whether it’s open fromaria-expanded, the active option througharia-activedescendant, and the number of results from the status element.
Single Select
- Epimedium
- Brunnera
- Pulmonaria
- Hellebore
- Heuchera
Code
<div class="field">
<label for="plant" class="field__label label">Plant</label>
<div class="combobox">
<div class="combobox__control">
<svg class="icon combobox__icon-start" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Search"/></svg>
<input id="plant" class="combobox__input" role="combobox" aria-expanded="false"
aria-controls="plant-list" aria-autocomplete="list" autocomplete="off" />
<button type="button" class="button button--plain button--icon combobox__clear" tabindex="-1" aria-label="Clear">
<svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Close"/></svg>
</button>
<button type="button" class="button button--plain button--icon combobox__toggle" tabindex="-1" aria-label="Show plants">
<svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#ChevronDown"/></svg>
</button>
</div>
<ul id="plant-list" class="combobox__listbox" role="listbox" popover="manual" aria-label="Plants">
<li id="plant-1" class="combobox__option" role="option" aria-selected="false">
<span>Epimedium</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plant-2" class="combobox__option" role="option" aria-selected="false">
<span>Brunnera</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plant-3" class="combobox__option" role="option" aria-selected="false">
<span>Pulmonaria</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plant-4" class="combobox__option" role="option" aria-selected="false">
<span>Hellebore</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plant-5" class="combobox__option" role="option" aria-selected="false">
<span>Heuchera</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
</ul>
<p class="sr-only" role="status"></p>
</div>
</div>
Multi-Select with Chips
- Brunnera
- Epimedium
- Pulmonaria
- Hellebore
- Heuchera
Code
<div class="field">
<label for="plants-multi" class="field__label label">Plants</label>
<div class="combobox">
<div class="combobox__control">
<span class="combobox__tags">
<span class="chip chip--subtle">
<span class="chip__text">Brunnera</span>
<button type="button" class="chip__remove" aria-label="Remove Brunnera">
<svg class="icon" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Close"/></svg>
</button>
</span>
</span>
<input id="plants-multi" class="combobox__input" role="combobox" aria-expanded="false"
aria-controls="plants-multi-list" aria-autocomplete="list" autocomplete="off" />
</div>
<ul id="plants-multi-list" class="combobox__listbox" role="listbox" popover="manual" aria-multiselectable="true" aria-label="Plants to add">
<li id="plants-multi-1" class="combobox__option" role="option" aria-selected="true">
<span>Brunnera</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plants-multi-2" class="combobox__option" role="option" aria-selected="false">
<span>Epimedium</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plants-multi-3" class="combobox__option" role="option" aria-selected="false">
<span>Pulmonaria</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plants-multi-4" class="combobox__option" role="option" aria-selected="false">
<span>Hellebore</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
<li id="plants-multi-5" class="combobox__option" role="option" aria-selected="false">
<span>Heuchera</span>
<svg class="icon combobox__check" width="1em" height="1em" aria-hidden="true" focusable="false"><use href="#Check"/></svg>
</li>
</ul>
<p class="sr-only" role="status"></p>
</div>
</div>
Chips sit before the input and wrap. Each remove Button names its value. The list stays open while choosing.
The Product’s Script
The library ships no script. The product’s script must:
- Show the list (
showPopover()) and setaria-expanded="true"on typing, ArrowDown or the toggle; hide it and setfalseon Escape, a single selection or leaving the field. - Filter: set
hiddenon options (and on groups with none left), show.combobox__emptywhen nothing matches, and put the count or “No results” in the status element. - Move the active option with ArrowUp and ArrowDown:
.is-activeon the option andaria-activedescendanton the input. Focus stays in the input. Scroll the option into view. - Select with Enter or a click (prevent the mousedown so focus stays in the input): set
aria-selected; fill the input (single) or add a Chip (multi). - Escape with the list closed clears the input. In multi-select, Backspace in an empty input removes the last Chip. After removing a Chip, return focus to the input.
Do and Don’t
| Do | Don’t |
|---|---|
| Use a native select or datalist when it does the job. | Reach for a Combobox for a short, known list. |
Name the field with a <label> and the list with aria-label. | Leave the list unnamed. |
| Put the result count in the status element. | Show a count that is never announced. |
Keep focus in the input and set aria-activedescendant. | Move focus into the options. |
Set aria-invalid or disabled on the input. | Look for an invalid or disabled class. |
| Say why an option is disabled. | Put links or Buttons inside options. |
In Storybook
See also
- Form Controls: The parts people fill in or set, such as text inputs, selects, checkboxes, switches and sliders.
- Field, Fieldset and Form: What goes around form controls, such as labels, hints, errors, groups of choices and the Form's actions.
- Badge, Status, Chip and Kbd: Small labels chosen by their job, such as a count on something, the state of something, a value someone chose or a key to press.
- Lockup: A primary line of text with a secondary one under it, such as a name and an email.