Combobox
Finds one or more values by typing.
Mode
singledefault
multiple
creatable
email
Sizes
sm
defaultdefault
lg
States
rest
focus
filled
invalid
disabled
pending
open
empty
searching
create row
Usage
When to use it
- Use
- More than about seven options, so typing beats scanning: pipelines, tables, queues, workers.
- Use
- Several values from one list, shown together (multiple).
- Use
- Values the list cannot know: new labels, the emails paged when a job fails (multiple creatable).
- Not for
- Seven or fewer options known up front. Use Select.
- Not for
- One line of free text with no list. Use Input.
Rules
9 rules
- Must
- Name the field with a Field and FieldLabel, or aria-label. Because a placeholder is not a name and goes on the first keystroke, so a screen reader hears only "combobox".
- Must
- Pass pending while results load, and filter={null} on Combobox when the server filters. Because without pending the list says No matches while results load, and the client filter drops and reorders server matches.
- Must
- Give multiple creatable a validate or a preset when the format matters. Because without one a typo like "ops@" commits as a valid tag.
- Should
- Name what was searched in emptyText.
- Should
- Set showClear on an optional single field. Because once a value is picked there is no other way back to empty.
- Must
- Pass the value as a plain string child of ComboboxChip. Because the remove button takes its name from string children only, so a tag holding an element gets a button named just "Remove".
- Never
- Use a combobox for seven options or fewer. Because typing to filter a list that already fits on screen is an extra step, and Select shows every option at once.
- Never
- Lift the tags field's height cap with className. Because a pasted list of two hundred emails would grow the field and push the rest of the form off the screen; it stops at four rows and scrolls.
- Never
- Draw a ring or a shadow on the field to show focus or the open list. Because the field shows focus itself, white with the gold edge doubled inward and a 1px pale halo, as Input does, and a ring outside that reads as a second box.
State order
6 states, highest first
- 1
- disabled
- 2
- invalid
- 3
- focus
- 4
- pending
- 5
- hover
- 6
- rest
Accessibility
5 checks
- The text is role="combobox" with aria-expanded, aria-controls, aria-activedescendant and aria-invalid; the list is a listbox, multiselectable with multiple.
- Adds, removes and Searching are announced in polite status regions.
- A marked tag says its reason in title and in screen reader text, and the field turns red, so colour is never the only cue.
- Each tag's remove button reads Remove and the value; the caret reads Show options and stays out of the tab order.
- Focus turns the field white with its edge --focus and a pale halo; inside the list the highlighted row lifts to white between two hairlines, and a chosen row carries an ink check, no fill.
Keyboard
5 keys
- Down, Up
- Opens the list and moves the highlight.
- Enter
- Picks the highlighted row, or adds the typed value when creatable.
- Escape
- Closes the list; a second Escape reaches the dialog around it.
- Backspace
- In the empty text of a tags field, removes the last tag.
- Left
- At the start of the text, moves into the tags.
Edge cases
8 edge cases
- Long tag
- Truncates inside the field with an ellipsis.
- Twenty-four tags
- They wrap; the field stops at four rows and scrolls.
- Pasted list
- Splits into tags; a quoted "Dee, Ops" <dee@acme.example> stays one address.
- Value typed twice
- Adds once; the text clears and the existing tag flashes the selection wash.
- items=[] and not creatable
- The empty text; with creatable, nothing until the person types.
- Slow search
- Keeps the Searching row until results arrive.
- Inside a form
- Enter picks or adds and does not submit while the list is open.
- Right to left
- Tags flow right to left; the check and the caret swap sides.
API
9 props
Combobox items, value, defaultValue, onValueChange, multiple, filterBase UIcreatable, presetboolean, 'email'defaultfalsenormalize, validate, split, delimiters(raw) => string, (v, all) => string | null, (text) => string[], string[]defaulttrim, none, commas and lines, [',']pendingbooleandefaultfalseComboboxFielditems, multiple, placeholder, emptyText, size, showClear, showSearchIconComboboxInput size'sm' | 'default' | 'lg'default'default'ComboboxChips size'default' | 'lg'default'default'ComboboxContent size, anchor'default' | 'sm', Refdefault'default'ComboboxChip value, showRemovestring, booleandefault, true
Tokens
33 tokens
--field-bg- #f8f7f3
--field-hover- #fefefd
--field-focus-bg- #ffffff
--field-ring-focus- #fff1b8
--field-ring-fail- #fbdcd8
--field-inset- 10px
--row-highlight- #ffffff
--row-highlight-edge- var(--line)
--well- #f4f3ee
--selected- #fff4bf
--line- #e2dfd5
--focus- #7a5d00
--fail- #b3261e
--fail-text- #b3261e
--fail-tint- #fdecea
--hover- rgb(22 21 18 / 0.04)
--text-1- #161512
--text-2- #55524a
--text-3- #6b675e
--control-sm- 26px
--control- 32px
--control-lg- 36px
--row-sm- 32px
--menu-min- 192px
--popup-max- 320px
--icon- 16px
--icon-sm- 14px
--pending-cell- 3px
--type-body-sm- 400 14px/20px var(--font-sans)
--type-code-sm- 400 12px/16px var(--font-mono)
--type-label-sm- 400 12px/16px var(--font-mono)
--type-caption- 400 13px/18px var(--font-sans)
--dur-state- 120ms