controls
Combobox
A text input with a list of suggestions that narrows as people type.
Rich widgetNeeds a script for its behavior.
Usage
An input with role="combobox" and a listbox of options with popover="manual". Typing filters the list; an option with aria-disabled="true" shows but cannot be chosen.
- Anna de Boer
- Bram Visser
- Chloé Martin
- Daan Smit
- Emma Mulder (on leave)
<div class="ca-field w-full max-w-sm">
<label class="ca-field__label" for="instructor">Instructor</label>
<div class="ca-combobox">
<input class="ca-input ca-input--md" id="instructor" name="instructor" type="text" role="combobox" aria-controls="instructor-options" aria-expanded="false" aria-autocomplete="list" autocomplete="off" />
<ul class="ca-combobox__listbox" id="instructor-options" role="listbox" aria-label="Instructors" popover="manual">
<li class="ca-combobox__option" id="instructor-anna" role="option">Anna de Boer</li>
<li class="ca-combobox__option" id="instructor-bram" role="option">Bram Visser</li>
<li class="ca-combobox__option" id="instructor-chloe" role="option">Chloé Martin</li>
<li class="ca-combobox__option" id="instructor-daan" role="option">Daan Smit</li>
<li class="ca-combobox__option" id="instructor-emma" role="option" aria-disabled="true">Emma Mulder (on leave)</li>
</ul>
</div>
</div>Examples
Values that differ from the label
Put the value to fill in on data-value when it differs from what the option shows.
- Station, main entrance
- College, bike shed
- Office park, gate B
Type part of a name, or any address.
<div class="ca-field w-full max-w-sm">
<label class="ca-field__label" for="pickup">Pick-up point</label>
<div class="ca-combobox">
<input class="ca-input ca-input--md" id="pickup" name="pickup" type="text" role="combobox" aria-controls="pickup-options" aria-expanded="false" aria-autocomplete="list" autocomplete="off" aria-describedby="pickup-hint" />
<ul class="ca-combobox__listbox" id="pickup-options" role="listbox" aria-label="Pick-up points" popover="manual">
<li class="ca-combobox__option" id="pickup-station" role="option" data-value="Station square 1">Station, main entrance</li>
<li class="ca-combobox__option" id="pickup-school" role="option" data-value="College road 12">College, bike shed</li>
<li class="ca-combobox__option" id="pickup-office" role="option" data-value="Harbour lane 8">Office park, gate B</li>
</ul>
</div>
<p class="ca-field__hint" id="pickup-hint">Type part of a name, or any address.</p>
</div>Options that are links
Options can be links (<a role="option" href>); Enter or a click follows the link. data-autoselect makes the first match active, so Enter takes it at once.
<div class="ca-field w-full max-w-sm">
<label class="ca-field__label" for="go-to-student">Go to a student</label>
<div class="ca-combobox" data-autoselect>
<input class="ca-input ca-input--md" id="go-to-student" type="text" role="combobox" aria-controls="go-to-student-options" aria-expanded="false" aria-autocomplete="list" autocomplete="off" />
<div class="ca-combobox__listbox" id="go-to-student-options" role="listbox" aria-label="Students" popover="manual">
<a class="ca-combobox__option" id="student-noor" role="option" href="#student-noor-bakker">Noor Bakker</a>
<a class="ca-combobox__option" id="student-lucas" role="option" href="#student-lucas-jansen">Lucas Jansen</a>
<a class="ca-combobox__option" id="student-sam" role="option" href="#student-sam-de-vries">Sam de Vries</a>
</div>
</div>
</div>Guidance and accessibility
When to use
- Use a combobox when people pick from a long list they know by name, such as instructors or addresses, and typing is faster than scrolling.
- People may still type a value that is not in the list. When only listed values are allowed, check the value when the form is sent.
- For a handful of fixed options, use a select or radio buttons.
Results from your own code
For search, give the root data-filter="none" and render the options
yourself on each input event, for example ranked results from an index.
The script follows the changes to the list and keeps the keyboard, the
active option and aria-expanded in step. Leave popover off the listbox
when the results are part of the page, such as in a search dialog: the list
then shows while it has options, and Escape is left to the dialog. The
search on this site works this way.
Accessibility
- It follows the WAI-ARIA combobox pattern with a listbox;
ca-combobox.jsprovides the behavior. Focus stays in the input, andaria-activedescendanttells screen readers which option is active. - ArrowDown opens the list and moves down, ArrowUp moves up, Enter fills in the active option, and Escape closes the list or, when it is closed, clears the input.
- Give the input a visible
<label>and the listbox anaria-label. - Set
autocomplete="off", so the browser’s own suggestions do not cover the list. - The list opens in the top layer, so a container with
overflow: hiddendoes not cut it off. Browsers without CSS anchor positioning show it in the middle of the screen.
Classes
Generated from the component contract. Parts use __, modifiers use --.
| Class | Kind | Use it for |
|---|---|---|
ca-combobox | Component | Wrapper around the input and its list; the list opens under the input. |
ca-combobox__listbox | Part | The role="listbox" of options, a manual popover anchored under the input. |
ca-combobox__option | Part | One role="option". The active one has aria-selected="true". |
Skins
The same markup, dressed by each skin. Structure and behavior stay the same; only the look changes.
Base skin
Neutral grayscale starting point; product skins extend it and change what differs.
- Anna de Boer
- Bram Visser
- Chloé Martin
- Daan Smit
- Emma Mulder (on leave)
Calaca skin
The Calaca UI brand: hueso paper and tinta ink, cempasúchil marigold, rosa and jade.
- Anna de Boer
- Bram Visser
- Chloé Martin
- Daan Smit
- Emma Mulder (on leave)
Proof skin
Pilot skin pushed far from base: square, heavy, offset shadows, larger targets.
- Anna de Boer
- Bram Visser
- Chloé Martin
- Daan Smit
- Emma Mulder (on leave)
Crossview skin
The CrossView proofing table: cool ground, bond-paper sheets, rule-grey hairlines, one red mark.
- Anna de Boer
- Bram Visser
- Chloé Martin
- Daan Smit
- Emma Mulder (on leave)
Framework notes
Link the prebuilt stylesheet of your skin next to your brand theme. No build step.
<link rel="stylesheet" href="theme.css" />
<link rel="stylesheet" href="ca-base.css" />In Astro, Vite and other Tailwind projects, import Calaca after Tailwind and your theme in your main stylesheet.
@import "tailwindcss";
@import "./theme.css";
@import "@calaca/ui/core.css";
@import "@calaca/ui/skins/base.css";Same stylesheet as above; write the classes in className.
<button className="ca-button ca-button--primary ca-button--md" type="button">Save changes</button>Load the combobox script once per page; it handles every combobox, also ones a framework adds later. In Astro or Next.js, import @calaca/ui/behavior/combobox once in the layout.
<script type="module" src="ca-combobox.js"></script>