Combobox
- Needs review
- WCAG 2.2 evidence
- RTL
A text field with a filtered list of options. Built on React Aria ComboBox: typing filters the
options (a locale-aware "contains" match, or defaultFilter), the arrow keys, Home/End and Page Up/Down move the
active option while focus stays in the field, Enter selects, Escape closes the list (and then clears the text);
React Aria announces the number of results and the selected option (WCAG 2.1.1, 4.1.2, 4.1.3). The label, helper and
error text are linked to the field; errors show as text with an icon, never by colour alone (1.4.1, 3.3.1). With
name, the selected key (or keys) is submitted with the form. selectionMode="multiple" keeps the list open while picking and shows the values as removable tags in the field.
Use Select for short fixed lists. Consumer duties: label and option wording; translated placeholder, empty and loading text; error text that says how
to fix the problem.
import { Combobox } from "@nexera-ui/react";- 10
- examples
- 43
- props
- 7
- live controls
- 1
- platform
- 14
- WCAG criteria
- 2
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { Combobox } from "@nexera-ui/react";
<Combobox />Examples 9
The same examples as Storybook, rendered live. Open Code to copy one.
States
Filled (defaultValue, with the clear button), Disabled and Error (isInvalid + errorMessage).
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function States() {
return (
<div className="flex flex-col gap-6">
<Combobox
label="Manager"
description="Approves leave and expenses"
defaultItems={people}
defaultValue="omar"
>
{personOption}
</Combobox>
<Combobox label="Backup manager" defaultItems={people} defaultValue="sara" isDisabled>
{personOption}
</Combobox>
<Combobox
label="Second approver"
placeholder="Search people"
defaultItems={people}
isInvalid
errorMessage="Choose a manager to continue"
>
{personOption}
</Combobox>
</div>
);
}
Open list
The list open, as wide as the field, with the keyboard hints. The story opens it with the chevron button; the arrow keys move the active option while focus stays in the field.
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function OpenList() {
return (
<div className="min-h-96">
<Combobox
label="Manager"
placeholder="Search people"
defaultItems={people}
defaultValue="sara"
>
{personOption}
</Combobox>
</div>
);
}
Multiple selection
Figma Selection=Multiple: the values are Badge-style tags in the field (remove button, or Delete on a focused tag), the options show checkboxes and the list stays open while picking.
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function MultipleSelection() {
return (
<Combobox
label="Skills reviewers"
placeholder="Search people"
selectionMode="multiple"
defaultItems={people}
defaultValue={["omar", "sara"]}
description="Each reviewer gets a task."
>
{personOption}
</Combobox>
);
}
Grouped
Grouped options with SelectSection and SelectGroupLabel.
import { Combobox, SelectGroupLabel, SelectOption, SelectSection } from "@nexera-ui/react";
export function Grouped() {
return (
<Combobox label="Location" placeholder="Search locations">
<SelectSection>
<SelectGroupLabel label="Offices" />
<SelectOption id="lahore" label="Lahore HQ" />
<SelectOption id="dubai" label="Dubai office" />
</SelectSection>
<SelectSection>
<SelectGroupLabel label="Cities" />
<SelectOption id="karachi" label="Karachi" />
<SelectOption id="istanbul" label="Istanbul" />
</SelectSection>
</Combobox>
);
}
Loading empty and create
Loading, empty and create through listboxProps: type a name that is not in the list to see the Create option, which adds it.
import { useState } from "react";
import { type Key } from "react-aria-components";
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function LoadingEmptyAndCreate() {
function Example() {
const [skills, setSkills] = useState([
{ id: "figma", name: "Figma" },
{ id: "react", name: "React" },
{ id: "a11y", name: "Accessibility" },
]);
const [value, setValue] = useState<Key[]>(["figma"]);
return (
<div className="flex flex-col gap-6">
<Combobox
label="Skills"
placeholder="Search or add skills"
selectionMode="multiple"
defaultItems={skills}
value={value}
onChange={setValue}
listboxProps={{
emptyDescription: "Check the spelling or add it",
onCreate: (text) => {
const id = text.toLowerCase();
setSkills((current) => [...current, { id, name: text }]);
setValue((current) => [...current, id]);
},
}}
>
{(skill) => <SelectOption id={skill.id} label={skill.name} />}
</Combobox>
<Combobox
label="Manager"
placeholder="Search people"
items={[]}
listboxProps={{ isLoading: true, loadingLabel: "Searching 128 people" }}
>
{personOption}
</Combobox>
</div>
);
}
return <Example />;
}
Controlled
Controlled value and text: value / onChange and inputValue / onInputChange.
import { useState } from "react";
import { type Key } from "react-aria-components";
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function Controlled() {
function Example() {
const [value, setValue] = useState<Key | null>("ali");
return (
<div className="flex flex-col gap-3">
<Combobox label="Manager" defaultItems={people} value={value} onChange={setValue}>
{personOption}
</Combobox>
<span className="text-body-small text-secondary">Value: {String(value)}</span>
</div>
);
}
return <Example />;
}
Composition
Composable parts: ComboboxRoot + FieldLabel + ComboboxTrigger + ComboboxListbox.
import {
ComboboxListbox,
ComboboxRoot,
ComboboxTrigger,
FieldLabel,
SelectOption,
} from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function Composition() {
return (
<ComboboxRoot defaultItems={people}>
<FieldLabel>Approver</FieldLabel>
<ComboboxTrigger placeholder="Search people" />
<ComboboxListbox<Person> keyboardHints={false} emptyDescription="Invite them first">
{personOption}
</ComboboxListbox>
</ComboboxRoot>
);
}
Long label
Long labels, helper texts and values wrap.
import { within } from "storybook/test";
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
const people: Person[] = [
{ id: "omar", name: "Omar Farooq", team: "Engineering" },
{ id: "sara", name: "Sara Khan", team: "Design" },
{ id: "ali", name: "Ali Raza", team: "Engineering" },
{ id: "hina", name: "Hina Malik", team: "People Operations" },
{ id: "bilal", name: "Bilal Ahmed", team: "Finance" },
{ id: "zara", name: "Zara Sheikh", team: "Sales" },
];
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function LongLabel() {
return (
<div className="max-w-[14rem]">
<Combobox
label="Manager who approves travel, leave and expense reports"
description="The approver is notified by email and in the app within five minutes."
defaultItems={people}
selectionMode="multiple"
defaultValue={["hina", "bilal"]}
>
{personOption}
</Combobox>
</div>
);
}
Right to left
Right-to-left: icons, padding, tags and the list mirror (logical properties).
import { Combobox, SelectOption } from "@nexera-ui/react";
interface Person {
id: string;
name: string;
team: string;
}
function personOption(person: Person) {
return <SelectOption id={person.id} label={person.name} description={person.team} />;
}
export function RightToLeft() {
return (
<div className="flex flex-col gap-6">
<Combobox
label="المدير"
placeholder="ابحث عن الأشخاص"
description="يوافق على الإجازات والمصروفات"
defaultItems={[
{ id: "omar", name: "عمر فاروق", team: "الهندسة" },
{ id: "sara", name: "سارة خان", team: "التصميم" },
]}
defaultValue="omar"
>
{personOption}
</Combobox>
<Combobox
label="المراجعون"
selectionMode="multiple"
defaultItems={[
{ id: "omar", name: "عمر فاروق", team: "الهندسة" },
{ id: "sara", name: "سارة خان", team: "التصميم" },
]}
defaultValue={["omar", "sara"]}
isInvalid
errorMessage="أضف مراجعًا واحدًا على الأقل"
>
{personOption}
</Combobox>
</div>
);
}
Props 43
Press "Try it" on a card to load that prop into the playground.
43 props shown
children*NexeraReactNode | ((item: T) => ReactNode)The options: SelectOption elements (optionally grouped in SelectSection with a SelectGroupLabel), or a function
that renders one option per item of items / defaultItems.
placeholderNexerastringHint inside the empty text field. Never the only label.
descriptionNexeraReactNodeHelper text under the field. Linked with aria-describedby. Replaced by the
error message while the field is invalid, as in the Figma Error state.
errorMessageNexeraReactNode | ((validation: ValidationResult) => ReactNode)Error text under the field, shown with an error icon while the field is invalid
(isInvalid, or a failed isRequired / validate check on submit) and linked with aria-describedby. Defaults to
the validation message. Say what is wrong and how to fix it ("Choose a manager to continue").
clearLabelNexerastringAccessible name of the clear button. Translate it for your locale.
selectedTagsLabelNexerastringAccessible name of the selected values (Selection=Multiple) when there is no visible label. Translate it for your locale.
listboxPropsNexeraOmit<ComboboxListboxProps<T>, "children" | "items">Props for the list (ComboboxListbox): isLoading and loadingLabel, emptyTitle and emptyDescription,
onCreate and createLabel, keyboardHints, placement.
classNameNexerastringExtra classes for the field (label, field box, helper text), merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the field.
allowsEmptyCollectionNexerabooleanWhether the list stays open when nothing matches, to show the Figma Empty state (or the Create option).
labelNexeraFieldLabelContentVisible label, linked to the control by React Aria (WCAG 1.3.1, 3.3.2). Short noun, for example "Work email". Wraps instead of truncating. A placeholder is never a substitute for it. Not set: the field has no visible label. Not set: the field has no visible label of its own.
aria-labelNexerastringAccessible name when it must differ from the visible label. Prefer the visible label (WCAG 2.5.3).
Accessible name. Required when there is no visible label; translate it.
Accessible name. Optional when aria-labelledby is set.
aria-labelledbyNexerastringId(s) of element(s) that name the field; wins over the visible label.
Id(s) of element(s) that name the field; wins over aria-label.
Id(s) of visible element(s) that name the field.
isDisabledReact AriabooleanWhether the input is disabled.
isInvalidReact AriabooleanWhether the input value is invalid.
defaultValueReact AriaKey | readonly Key[] | nullThe default value (uncontrolled).
autoFocusReact AriabooleanWhether the element should receive focus on render.
idReact AriastringThe element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
aria-describedbyReact AriastringIdentifies the element (or elements) that describes the object.
onChangeReact Aria(value: ChangeValueType<M>) => voidHandler that is called when the value changes.
isReadOnlyReact AriabooleanWhether the input can be selected but not changed by the user.
validationBehaviorReact Aria"native" | "aria"Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA.
shouldFocusWrapReact AriabooleanWhether keyboard navigation is circular.
defaultItemsReact AriaIterable<T>The list of ComboBox items (uncontrolled).
itemsReact AriaIterable<T>The list of ComboBox items (controlled).
onOpenChangeReact Aria(isOpen: boolean, menuTrigger?: MenuTriggerAction) => voidMethod that is called when the open state of the menu changes. Returns the new open state and the action that caused the opening of the menu.
selectionModeReact Aria"single" | "multiple"Whether single or multiple selection is enabled.
selectedKeyReact AriaKey | nullThe currently selected key in the collection (controlled). @deprecated
defaultSelectedKeyReact AriaKey | nullThe initial selected key in the collection (uncontrolled). @deprecated
onSelectionChangeReact Aria(key: Key | null) => voidHandler that is called when the selection changes. @deprecated
inputValueReact AriastringThe value of the ComboBox input (controlled).
defaultInputValueReact AriastringThe default value of the ComboBox input (uncontrolled).
onInputChangeReact Aria(value: string) => voidHandler that is called when the ComboBox input value changes.
allowsCustomValueReact AriabooleanWhether the ComboBox allows a non-item matching input value to be set.
menuTriggerReact Aria"focus" | "input" | "manual"The interaction required to display the ComboBox menu.
disabledKeysReact AriaIterable<Key>The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
valueReact AriaKey | readonly Key[] | nullThe current value (controlled).
isRequiredReact AriabooleanWhether user input is required on the input before form submission.
validateReact Aria(value: ComboBoxValidationValue<M>) => true | ValidationError | nullA function that returns an error message if a given value is invalid.
Validation errors are displayed to the user when the form is submitted
if validationBehavior="native". For realtime validation, use the isInvalid
prop instead.
nameReact AriastringThe name of the input element, used when submitting an HTML form. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname).
formReact AriastringThe <form> element to associate the input with.
The value of this attribute must be the id of a <form> in the same document.
See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#form).
defaultFilterReact Aria(textValue: string, inputValue: string) => booleanThe filter function used to determine if an option should be included in the combo box list.
By default, a language-sensitive "contains" filter from useFilter is used.
formValueReact Aria"text" | "key"Whether the text or key of the selected item is submitted as part of an HTML form. When
allowsCustomValue is true, this option does not apply and the text is always submitted.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
8 direct · 6 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.4Resize TextLevel AA · supporting test
- 1.4.11Non-text ContrastLevel AA · supporting test
- 1.4.12Text SpacingLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.3Focus OrderLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · supporting test
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 3.3.1Error IdentificationLevel A · tested directly
- 3.3.2Labels or InstructionsLevel A · tested directly
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-focus-visibledata-hovered
<Combobox className="data-disabled:opacity-90 shadow-sm" />Used in blocks
Related components
- CalendarDayOne day of a {@link CalendarMonth } grid, built on React Aria `CalendarCell`: a `gridcell` whose button is named by the full, localised date ("Wednesday, October 14, 2026"), with `aria-selected`, `aria-disabled` and the "today" and range descriptions read by screen readers (WCAG 1.3.1, 4.1.2).
- CalendarMonthA month calendar for picking a date or a date range: header with previous / next buttons and the localised month name, weekday row, and six weeks of `CalendarDay`s.
- CheckboxThe bare 18 px checkbox: one independent yes/no choice applied on submit, or a row selector in a table, list or tree.
- CheckboxCardA large checkbox option with an icon, a title and a description, for a few options that need explanation (notification channels, benefits).
- CheckboxFieldA checkbox with a label and optional helper text: the box (the same element as `Checkbox`) followed by the label and description, applied on submit.
- ColorInputA hex colour field with a swatch and a picker popover.
- ColorPickerPicks a colour.
- ColorTokenCardDocumentation card for one colour token: a 96 px colour sample with an "Aa" text sample, then the name, token path, hex value and contrast note as text.