Combobox

User Interface (UI)

How a combobox looks, from its parts and its sizes to one or several values, its states and the tokens it shares with the Select.

Let's start with how a combobox looks: its parts, one or several values, groups, icons, sizes, its states and the variables that restyle each part.

Anatomy

  1. Field — The root, a <div> that draws the border, the background and the focus ring, and holds the parts below.
  2. Leading icon — Optional. An icon before the text, hidden from assistive technologies.
  3. Icon — Optional. An icon for each option, shown in the panel, beside the picked option's label and in each tag, hidden from assistive technologies.
  4. Text — The <input> people type in, which has the combobox role and shows the picked option's label.
  5. Toggle — A <button> with the chevron, which opens and closes the panel and turns as it does. It's out of the tab order.
  6. Tag — With multiple, one per value, with its own remove button, on a row under the text.
  7. Panel — The floating listbox, with its own scroll boundary, in the top layer while it's open.
  8. Group label — Optional. A heading for a group of options.
  9. Option — One suggestion, with its hover, highlighted, selected and disabled states, and a check mark when it's picked.
  10. Separator — Optional. A rule between options or groups.
  11. Create option — With creatable, a last option that adds what people typed. In its place, the panel says "No results" when nothing matches.

The field is the root, as on the Select, and the <input> fills its first row, under the icons, so it lines up with an Input of the same size. The panel, its options, its groups, its separators and the tags are the Select's, drawn by the same code with the same tokens, so the two components look the same wherever they meet. You can see the order of the parts in the DOM contract.

One value or several

A Combobox picks one value by default, and shows its label in the text field. With multiple, it picks any number, and each one becomes a tag on a row under the text, which grows onto more lines as tags wrap. The text clears after each pick, so people can search for the next one, and the panel stays open while they do.

FranceSpain
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Field label="Countries">
  <Combobox id="countries" name="countries" multiple options={countries} placeholder="Type a country" />
</Field>

We put the tags under the text, rather than before it on the same row, since people need to see what they type as they type it, however many values they've picked. The tags come after the text in the page too, so Tab reaches the remove buttons in the order people read them, the way the Select's do.

Groups and separators

Options can sit in groups, each with a heading, and a separator can go between groups or options, the same list the Select takes. As people type, a group with no match hides along with its heading, and so does a separator with nothing on one side of it, so the panel never shows an empty heading or two rules in a row.

France
Spain
New Zealand
Australia
<Field label="Destination">
  <Combobox id="destination" name="destination" options={destinations} placeholder="Type a country" />
</Field>

Icons

An option can have an icon, like a label's symbol or a country's flag, which helps people scan the suggestions and recognise what they picked. The icon slot gets each option, and the Combobox shows what it returns before the option's label in the panel, before the text while it shows the picked option's label and in each tag, the way the Select does, so a picked value looks the same in both.

Bug
Feature
Docs
Question
BugDocs
Bug
Feature
Docs
Question
const icons = { bug: BugIcon, feature: SparklesIcon, docs: BookOpenIcon, question: CircleHelpIcon };

<Field label="Labels">
  <Combobox id="labels" name="labels" multiple options={labels} placeholder="Type a label"
    icon={({ option }) => {
      const Icon = icons[option.value];
      return <Icon />;
    }}
  />
</Field>

Icons are decoration, so they're hidden from assistive technologies, and the label alone names each option (WCAG 1.1.1). Keep it that way, since an icon that means something the label doesn't say is lost on anyone who can't see it. Once people type, the text no longer names the picked option, so its icon leaves the field until the text shows its label again, and text people kept with allowCustomValue has no option, so it has no icon. The icon comes through a slot rather than a field in options, so it works with whatever icon library you use, and the options stay plain data you can send from a server.

Sizes

There are five sizes, xs, sm, md, lg and xl, the same as the Input and the Select, so a combobox lines up with them and with a Button in a row. The size also sets the panel's text and the tags' height, so they match the field they come from.

France
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
France
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
France
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
France
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
France
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Field label="Country" size="xs">
  <Combobox id="country" name="country" multiple options={countries} value={["fr"]} />
</Field>

Keep in mind that on touch screens the text is never smaller than 16px, whatever the size, or Safari on iOS zooms the page in when the text field gets focus, as with the Input.

States

The field has the Input's states, and the options have the Select's:

  • Hover, on pointers that can hover, darkens the border, and an option under the pointer gets a background.
  • Focus draws a ring around the whole field while the text has focus, and the highlighted option, the one the arrow keys moved to, gets a 2px outline of the same colour, since focus stays in the text.
  • Open turns the chevron.
  • Disabled dims the field to 50%, and leaves it out of the form.
  • Read-only draws a dashed border, keeps the value and its contrast, and doesn't open.
  • Invalid draws a second pixel of border in the error colour, while the Field shows the message.
  • A selected option is bolder and has a check mark, so it never rests on one cue, and a disabled option is at 50%.
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom

Pick a country we ship to.

<Field label="Disabled" disabled>
  <Combobox id="disabled" name="disabled" options={countries} value="fr" />
</Field>
<Field label="Read-only">
  <Combobox id="readonly" name="readonly" options={countries} value="fr" readonly />
</Field>
<Field label="Invalid" error="Pick a country we ship to.">
  <Combobox id="invalid" name="invalid" options={countries} placeholder="Type a country" />
</Field>

The focus ring and the highlight take the theme's --color--focus, the same as every other component's focus, so they change with your theme rather than with a variable of the Combobox's. Each state reads the attribute that exposes it, like aria-invalid or aria-selected, so what people see and what assistive technologies report can't drift apart.

Tokens

The Combobox reads the Select's variables for every part the two draw alike: the field, its sizes, the panel, the options, the group labels, the separators and the tags. So a class that sets --select--option--radius rounds the options of both, and there's one place to change how a listbox looks across your product. That's what the Design Tokens page's tokens/component-scope allows for parts a component shares, and the contract lists them under shared.

PartShared tokens, from the Select
Field--select--height, --select--padding, --select--gap, --select--font-size, --select--radius, --select--border-color, --select--background, --select--icon--size, --select--leading--color, --select--value--placeholder-color
Arrow--select--arrow--size, --select--arrow--color
Panel--select--panel--offset, --select--panel--padding, --select--panel--max-height, --select--panel--radius, --select--panel--border-color, --select--panel--background, --select--panel--box-shadow
Option--select--option--padding-block, --select--option--padding-inline, --select--option--radius, --select--option--color, --select--option--hover--background, --select--option--selected--background, --select--option--selected--font-weight, --select--option--disabled--opacity
Tag--select--tag--height, --select--tag--padding, --select--tag--radius, --select--tag--font-size, --select--tag--background, --select--tag--color, --select--tag--icon--size
Group label--select--group-label--padding-block, --select--group-label--padding-inline, --select--group-label--font-size, --select--group-label--font-weight, --select--group-label--color
Separator--select--separator--color, --select--separator--margin

The Combobox has variables of its own only for what the Select doesn't draw, the "No results" text:

TokenWhat it styles
--combobox--empty--padding-blockThe space above and below "No results"
--combobox--empty--padding-inlineThe space before and after it, the options' own
--combobox--empty--colorIts colour, the text colour at 70%, which has 4.5:1 like a placeholder

Here's one class, on a Combobox and on a Select, that restyles the field, the panel, the options and the tags of both:

FranceSpain
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox id="countries" name="countries" className="brand-field" multiple options={countries} />
<Select id="country" name="country" className="brand-field" options={countries} />

/* brand.css */
.brand-field {
  --select--radius: 1.25rem;
  --select--border-color: var(--color--primary);
  --select--panel--radius: 1rem;
  --select--panel--border-color: var(--color--primary);
  --select--option--radius: 1rem;
  --select--option--selected--background: color-mix(in srgb, var(--color--primary) 15%, transparent);
  --select--tag--radius: 1rem;
  --select--tag--background: var(--color--primary);
  --select--tag--color: var(--color--primary-contrast);
}

Set the variables from a class of yours, never with an inline style, since the components' styles sit in the components cascade layer, and a class outside it wins without a fight. The Design Tokens page explains why.

The sections below restyle each part on its own, with its variables and, where it has one, its slot. The leading icon restyles with the field, and the option icons have their own section.

Field

The root draws the field, so its tokens set the border, the background and the radius, and the leading slot adds an icon before the text. There's no value slot, as the Select has, since the text field shows the picked option's label and people edit it as they type.

Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox className="brand" id="country" name="country" options={countries} leading={<GlobeIcon />} />
.brand {
  --select--radius: 1.25rem;
  --select--border-color: var(--color--primary);
  --select--background: color-mix(in srgb, var(--color--primary) 8%, transparent);
}

Panel

The panel has no slot, so its tokens do the work: they set its radius, padding, border, background and shadow, and how far it sits from the field.

Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox className="brand" id="country" name="country" options={countries} />
.brand {
  --select--panel--radius: 1rem;
  --select--panel--padding: 0.5rem;
  --select--panel--border-color: var(--color--primary);
}

Option

The option slot changes what goes in an option, and the check mark stays beside it.

Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France (yours)
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox className="brand" id="country" name="country" options={countries} option={({ option, selected }) => <>{option.label}{selected && " (yours)"}</>} />
.brand {
  --select--option--radius: 1rem;
  --select--option--hover--background: color-mix(in srgb, var(--color--primary) 15%, transparent);
  --select--option--selected--background: color-mix(in srgb, var(--color--primary) 15%, transparent);
}

Tag

The tag slot changes what a tag says, and the remove button stays beside it, so it keeps its name. These are the Select's tags, so they look like the Tag, size for size.

FRES
Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox className="brand" id="country" name="country" multiple options={countries} tag={({ option }) => <>{option.value.toUpperCase()}</>} />
.brand {
  --select--tag--radius: 1rem;
  --select--tag--background: var(--color--primary);
  --select--tag--color: var(--color--primary-contrast);
}

Toggle

The arrow slot swaps the toggle's chevron for an icon of your own, which still turns as the panel opens. Its tokens, the Select's arrow tokens, set its size and colour, and the button around it keeps its name and its press area.

Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox className="brand" id="country" name="country" options={countries} arrow={<ChevronsUpDownIcon />} />
.brand {
  --select--arrow--color: var(--color--primary-text);
  --select--arrow--size: 1.25rem;
}

Group label

The group-label slot gets each group's label and changes how it reads, and its tokens set its padding, size, weight and colour.

France
Spain
New Zealand
Australia
<Combobox className="brand" id="country" name="country" options={destinations} groupLabel={({ label }) => <>{label} shipping</>} />
.brand {
  --select--group-label--color: var(--color--primary-text);
  --select--group-label--font-weight: 700;
}

Separator

The separator slot draws the rule between options or groups, and its tokens set its colour and the space around it.

France
Spain
New Zealand
Australia
<Combobox className="brand" id="country" name="country" options={destinations} separator={<span className="rule" />} />
.brand {
  --select--separator--color: var(--color--primary);
  --select--separator--margin: 0.5rem;
}

No results

The "No results" text is the one part the Select doesn't draw, so its tokens are the Combobox's own, and the emptyLabel prop changes what it says. Type something no country matches, like "xyz", to see it.

Austria
Belgium
Côte d'Ivoire
Curaçao
Denmark
Finland
France
Germany
Iceland
Ireland
Italy
Netherlands
New Zealand
Norway
Portugal
Réunion
São Tomé and Príncipe
Spain
Sweden
Switzerland
Türkiye
United Kingdom
<Combobox className="brand" id="country" name="country" options={countries} emptyLabel="No countries match" />
.brand {
  --combobox--empty--padding-block: 0.75rem;
  --combobox--empty--color: var(--color--primary-text);
}

UI Checklist

RuleLevelScopeRequirementBasisCheck
combobox/visible-focusMustComponentThe field shows a focus ring while its text has focus, and the highlighted option an outline at least 2px thickWCAG 2.4.7 (AA)Keyboard, Visual regression test
combobox/selected-not-colourMustComponentA picked option shows a check mark as well as its weight, and its tag, with multipleWCAG 1.4.1 (A)Visual regression test
combobox/contrastMustBothThe text, the placeholder and "No results" have 4.5:1, and the border, the chevron and the focus ring 3:1, in light and dark modeWCAG 1.4.3, 1.4.11 (AA)Contrast checker
combobox/target-sizeMustComponentThe toggle and each remove button are at least 24 × 24px to press, and 44px tall where a pointer is coarse, like the optionsOpen Components, beyond WCAG 2.5.8 (AA)Emulation, Review
combobox/shared-tokensShouldComponentThe field, the panel, the options, the group labels, the separators and the tags read the Select's variables, so one class restyles bothOpen ComponentsReview
combobox/sizesShouldComponentIts five sizes are the Input's and the Select's, and the panel's text and the tags follow the field's sizeOpen ComponentsVisual regression test
combobox/text-size-touchMustComponentIts text is at least 16px where any pointer is coarse, so Safari doesn't zoom in on focusOpen ComponentsEmulation
combobox/no-layout-shiftMustComponentOpening the panel, filtering and picking move nothing else on the pageOpen ComponentsVisual regression test
combobox/reflowMustComponentThe field and the panel fit a 320px-wide screen without scrolling sideways, and long labels wrapWCAG 1.4.10 (AA)Emulation
combobox/reduced-motionShouldComponentThe chevron and the panel don't animate when people ask for less motionWCAG 2.3.3 (AAA)Emulation
combobox/forced-colorsShouldComponentFocus, the highlight, the panel's edge and disabled options stay visible in forced coloursOpen ComponentsEmulation

FAQ

Copyright © 2026