Combobox

A text field that filters a list of options as people type, to choose one option or, as chips, several.

StableSince 1.0.0
On this page

Import

import { Combobox } from '@envsemble/envui';

Usage

Combobox takes its options as items, each with a value and a label, and optionally a description and start content, such as an Avatar; a ComboboxGroup puts items under a heading. An entry with an items array is a group, so an option has no field of that name.

  • Control it with value, null for none, and onValueChange, which receives the value and its item, or leave it uncontrolled with defaultValue.
  • label, description and error work as on TextField.
  • clearable adds a clear button while there is a value, and Esc on the closed field clears it too. Beside chips, it shows with a mouse only while the field is under the pointer or has focus; on a touch screen it always shows.
ChooseOwner.tsx
import { useState } from 'react';
import { Avatar, Combobox, type ComboboxItem } from '@envsemble/envui';

const people: ComboboxItem[] = [
  'Alex Morgan',
  'Sam Lee',
  'Priya Shah',
  'Jordan Ellis',
  'Taylor Kim',
  'Riley Chen',
].map((name) => {
  const handle = name.toLowerCase().replace(' ', '.');
  return {
    value: handle,
    label: name,
    description: `${handle}@example.com`,
    start: <Avatar name={name} />,
  };
});

export default function ChooseOwner() {
  const [owner, setOwner] = useState<string | null>('alex.morgan');
  return (
    <div style={{ inlineSize: 'min(100%, 24rem)' }}>
      <Combobox
        label="Owner"
        placeholder="Type a name"
        description="The person who answers questions about the plan."
        items={people}
        value={owner}
        onValueChange={setOwner}
        clearable
      />
    </div>
  );
}

Several options

With multiple, value is an array of values, each shown as a chip with a remove button, and the list stays open as people choose.

<Combobox
  multiple
  label="Reviewers"
  placeholder="Add people"
  items={people}
  value={reviewers}
  onValueChange={setReviewers}
/>

Search on a server

By default the list shows the options whose label or description contains the typed text, ignoring case and accents, in locale; filter takes a function of your own.

  • With filter={null} it shows items as given: search from onInputValueChange, which receives the typed text.
  • Set loading while the search runs, which shows loadingLabel instead of emptyLabel.
  • onOpenChange can load the options as the list opens; its second argument, ComboboxOpenChangeDetails, says why it opens or closes.
<Combobox
  label="Contact"
  items={results}
  filter={null}
  onInputValueChange={setQuery}
  loading={searching}
  loadingLabel="Searching…"
  emptyLabel={query ? 'No contacts found' : 'Type a name to search'}
  value={contact}
  onValueChange={setContact}
/>

In a form

name submits the value, or one entry for each chip, and a form reset restores defaultValue.

  • readOnly lets people focus the field and browse the list without changing the value.
  • Validation and its error belong to the application, such as an error set on submit when new FormData(form).getAll('topics') is empty.
<Combobox
  multiple
  name="topics"
  label="Topics"
  items={topics}
  defaultValue={['accessibility']}
  required
  error={error}
/>

Sizes

  • size is sm, md or lg; without it, the field takes its container's control height.
  • With visibleLabel={false}, a placeholder and an aria-label name it.
<Combobox
  size="sm"
  visibleLabel={false}
  aria-label="Document"
  placeholder="Document"
  items={documents}
/>

Composition

The field is env-field with env-combobox: the control, env-combobox-control, holds the chips, env-combobox-chip, with their labels, env-combobox-chip-label, and remove buttons, env-combobox-chip-remove, IconButtons with a cross; then the input, env-combobox-input, the clear button, env-combobox-clear, and the trigger, env-combobox-trigger. The list is the shared popup, env-combobox-popup, and its options are its rows, env-combobox-item, with the check, env-combobox-item-indicator. className and style reach the field's root, and ref the input.

Customize

Its popup is frosted, as every popup is: --env-popup-opacity, --env-popup-blur and --env-popup-shadow, set on a theme, change it, as Popover describes.

Accessibility

You provide

  • A label, or aria-label on a field without one.
  • The options, and with filter={null} the search behind them.
  • Validation and its error message.
  • clearLabel, removeLabel, loadingLabel and emptyLabel in the application's language.

envUI provides

  • The combobox pattern: a combobox field with a listbox of options and the highlighted one as its active descendant.
  • The label, description and error tied to the field, and aria-invalid.
  • Remove buttons named after their option, such as Remove Alex Morgan.
  • A status that announces loading and no matches.

Props

Combobox

A text field that filters a list of options as people type, for choosing one option or several. Chosen options show as removable chips; keyboard, focus and announcements follow the WAI-ARIA combobox pattern.

PropTypeDescription
ref
Ref<HTMLInputElement>
The text input, so that a form library can register it and focus it.
className
string
The field's class, on its root element, beside env-field and env-combobox.
style
CSSProperties
Styles of the field's root element.
itemsrequired
readonly Item[]readonly ComboboxGroup<Item>[]
The options, or groups of options under headings, in display order.
filter
ComboboxFilter<Item>null
Which options match the typed text: by default those whose label or description contains it, ignoring case and accents. null shows the items as given, for searching on the server.
onInputValueChange
(query: string) => void
Receives the typed text, and an empty string once it is cleared, such as to search a server.
loadingDefault false
boolean
The options are loading: the popup shows loadingLabel instead of emptyLabel.
loadingLabelDefault 'Loading…', or EnvUIProvider's messages.combobox.loading
ReactNode
Shown in the popup while loading.
emptyLabelDefault 'No matches', or EnvUIProvider's messages.combobox.empty
ReactNode
Shown in the popup when no option matches.
clearableDefault false
boolean
A clear button removes the value while there is one; Esc on the closed field clears it too.
clearLabelDefault 'Clear', or EnvUIProvider's messages.combobox.clear
string
Accessible name of the clear button.
removeLabelDefault Remove followed by the label, or EnvUIProvider's messages.combobox.remove
(label: string) => string
Accessible name of a chip's remove button, from the chip's label.
open
boolean
Whether the list is open; with `onOpenChange` it controls the popup.
defaultOpenDefault false
boolean
Whether an uncontrolled list starts open; closed by default.
onOpenChange
(open: boolean, details: ComboboxOpenChangeDetails) => void
Called as the popup opens and closes, and why, such as to load the options as it opens.
locale
string
The locale of the default filter's matching: EnvUIProvider's, else the browser's.
label
ReactNode
The field's visible name, such as Project name; it also names the control for assistive technology.
description
ReactNode
Lasting guidance under the field, such as a format or a limit, linked to the control as its description.
error
ReactNode
A message under the field that says how to correct the value. While it is set, the control is invalid (aria-invalid) and the message joins its description, after the description.
size
'sm''md''lg'
The control height, sm, md or lg; without it, the container's, and else md. sm is for a field with its label hidden.
visibleLabelDefault true
boolean
The label shows in the field, where it rests while the field is empty and floats into the outline while the field is in use or has a value. false hides it, for a field whose purpose is clear without it, such as a search in a toolbar: the label still names the field for assistive technology, and a placeholder gives the visible guidance.
multiple
boolean
Several options can be chosen; they show as chips in the field.
value
stringnullreadonly string[]
The chosen option's value, or null for none.
defaultValue
stringnullreadonly string[]
The value an uncontrolled field starts with, and that a form reset restores; none by default.
onValueChange
((value: string | null, item: Item | null) => void)((value: string[], items: (Item | ComboboxItem)[]) => void)
Receives the chosen value and its option, or null twice once the field is cleared.

Also accepts the native props of <input>, such as ref, className and aria-* attributes.

Part names

Each part's class and state attributes are public and versioned, for an application's CSS to style.

env-combobox
The combobox field.
env-combobox-chip
A chosen value as a chip, in a multiple combobox.
env-combobox-chip-label
A chip's text.
env-combobox-chip-remove
The button that removes a chip.
env-combobox-clear
The button that clears the value.
env-combobox-control
The well that holds the chips, the input and the buttons.
env-combobox-empty
The message when no option matches.
env-combobox-end
The buttons at the end of the well.
env-combobox-group
A group of options.
env-combobox-group-label
A group's label.
env-combobox-input
The text input.
env-combobox-item
One option.
env-combobox-item-indicator
The check of a chosen option.
env-combobox-label
The label.
env-combobox-list
The list of options.
env-combobox-popup
The popup with the options.
env-combobox-start
The icon at the start of the well, or of an option in the popup.
env-combobox-status
The announcement of the number of options.
env-combobox-trigger
The button that opens the list.
env-combobox-value-icon
The chosen option's icon in the well.
data-action-count
1 · 2 on env-combobox-control
data-disabled
Any value, or present on env-combobox-item
data-popup-open
Any value, or present on env-combobox-trigger
data-selected
Any value, or present on env-combobox-item

Color properties

Each sets a color of one instance, through style or CSS; its hover, focus and press follow it. They are public and versioned.

--env-combobox-chip-container
The fill of a chosen option’s chip.
--env-combobox-chip-content
The chip's label and remove button.

Strings

EnvUIProvider's messages overrides each, and a component's own label prop wins over it.

messages.combobox.remove
({ label }) => string A chip's remove button, such as Remove Alex Morgan.
messages.combobox.loading
"Loading…" Shown in the popup while options load.
messages.combobox.empty
"No matches" Shown in the popup when no option matches.
messages.combobox.clear
"Clear" The clear button's name.

Changelog

1.0.0

showLabel is visibleLabel, named after the label that visibleLabel={false} hides.

hint is description and variant="outlined" is variant="outline". className and style style the field's root rather than the input, which takes ref. Read-only chips no longer carry aria-readonly, which the input reports. The popup is the shared popup and the options its rows, and a chip's remove button is an IconButton with a cross: env-combobox-well is env-combobox-control, env-combobox-option env-combobox-item and env-combobox-check env-combobox-item-indicator, and the icons at the start, env-combobox-leading and env-combobox-value-leading, are env-combobox-start and env-combobox-value-icon, as other components name their start slots. The loading, empty, clear and remove labels come from EnvUIProvider's messages. An option's leading content is start, as on List.Item.

1.0.0-prerelease.6

The chevron turns over --env-motion-duration-medium with --env-motion-ease-enter as the list opens, and back in --env-motion-duration-fast with --env-motion-ease-exit, done when the list is.

The list opens over --env-motion-duration-medium, fading in and growing from 98 % with --env-motion-ease-enter, rather than growing from 96 % over the slow duration, so it shows from its first frames rather than late.

The chevron turns half a circle as the list opens and closes, rather than flipping over.

Beside chips, the clear button shows with a mouse only while the field is under the pointer or has focus, and each chip's cross is a little smaller, so the field's end holds one cross at rest; the clear button and the chevron stand a step apart.

While its list is open, the field keeps its focused frame, the accent edge and label, also when focus is in the popup, as when it opens under the pointer.

An avatar in the field and in the options takes an avatar's small size, large enough for its initials, rather than a disc of its color alone; where initials don't fit, in a small field at the compact scale and in a chip, an avatar of initials is left out, while an icon, a photo or a figure stays. A read-only field keeps its chevron, muted, as a read-only Select does.

A multiple field stays one row tall while its chips fit there; the input moves to a row of its own only once its text no longer fits.

The floating variant and the variant prop are gone: the field is outlined, and takes size="lg" in a long form on a page. The field takes a text area's rounded corners rather than round ends. A small one with a visible label takes the medium height, so its floated label and its value each have room, and says so in the console while developing: sm is for a field with its label hidden.

1.0.0-prerelease.5

The popup is frosted: the popup's color lets a little of what is behind through, blurred, with a soft shadow and no outline edge but a faint hairline in dark, and is solid with its edge at high contrast and with reduced transparency. Options are the md control height in body text, chosen ones take the selection's fill as well as their check mark, and groups part by space. Group labels sit a little ahead of the rows, and options lie one under the next with no space between them. A chosen option's check mark ends its row, after the label.