Select
- Needs review
- WCAG 2.2 evidence
- RTL
- Web · iOS · Android
Picks one or several values from a list. Built on React Aria
Select + Button + SelectValue + Popover + ListBox: the label, helper and error text are linked to the trigger;
Enter, Space and the arrow keys open the list with focus on the selected (or first) option; arrows, Home/End and
type-ahead move; Enter, Space or a click selects; Escape closes and returns focus to the trigger (WCAG 2.1.1, 2.4.3,
4.1.2). Errors show as text with an icon under the field, never by colour alone (1.4.1, 3.3.1). A hidden native
<select> carries the value in form submissions (name) and native validation (isRequired). selectionMode="multiple" keeps the list open while picking and shows the values as removable SelectTags in the
field. Use Radio group or Segmented control for fewer than four options and Combobox for long lists that need search. Consumer duties: label and option wording; error text that says how to fix the problem.
import { Select } from "@nexera-ui/react";- 11
- examples
- 39
- props
- 7
- live controls
- 3
- platforms
- 14
- WCAG criteria
- 14
- 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 { Select } from "@nexera-ui/react";
<Select />Examples 10
The same examples as Storybook, rendered live. Open Code to copy one.
Sizes
Every Figma Size (32, 40 and 48 px), with the optional leading icon.
import { Select } from "@nexera-ui/react";
import { LuBuilding2 } from "react-icons/lu";
const sizes = ["sm", "md", "lg"] as const;
export function Sizes() {
return (
<div className="flex flex-col gap-6">
{sizes.map((size) => (
<Select
placeholder="Select department"
description="Used for approvals and reports."
key={size}
size={size}
label={`Department (${size})`}
leadingIcon={<LuBuilding2 />}
{...(size === "sm" ? {} : { defaultValue: "eng" })}
/>
))}
</div>
);
}
States
Semantic states: Filled (defaultValue), Disabled (isDisabled) and Error (isInvalid + errorMessage).
import { Select } from "@nexera-ui/react";
export function States() {
return (
<div className="flex flex-col gap-6">
<Select
label="Department"
placeholder="Select department"
description="Used for approvals and reports."
defaultValue="eng"
/>
<Select
label="Department"
placeholder="Select department"
description="Used for approvals and reports."
isDisabled
defaultValue="design"
/>
<Select
label="Department"
placeholder="Select department"
description="Used for approvals and reports."
isInvalid
errorMessage="Select a department to continue."
/>
</div>
);
}
Open list
The list open: as wide as the trigger, groups with SelectGroupLabel, descriptions, leading icons and a disabled option. The selected option shows a check mark. Escape closes it and returns focus to the trigger.
import { Select, SelectGroupLabel, SelectOption, SelectSection } from "@nexera-ui/react";
import { LuBuilding2 } from "react-icons/lu";
export function OpenList() {
return (
<div className="min-h-96">
<Select label="Office" defaultValue="lahore" defaultOpen leadingIcon={<LuBuilding2 />}>
<SelectSection>
<SelectGroupLabel label="Pakistan" />
<SelectOption
id="lahore"
label="Lahore HQ"
description="Gulberg III"
leading={<LuBuilding2 />}
/>
<SelectOption id="karachi" label="Karachi" leading={<LuBuilding2 />} />
</SelectSection>
<SelectSection>
<SelectGroupLabel label="Gulf" />
<SelectOption id="dubai" label="Dubai" leading={<LuBuilding2 />} />
<SelectOption
id="doha"
label="Doha (opening 2027)"
leading={<LuBuilding2 />}
isDisabled
/>
</SelectSection>
</Select>
</div>
);
}
Multiple selection
selectionMode="multiple": options show the Figma Multi checkbox, the list stays open while picking, and the values appear as removable SelectTags in the field (remove button, or Delete on a focused tag).
import { Select, SelectOption } from "@nexera-ui/react";
function Departments() {
return (
<>
<SelectOption id="eng" label="Engineering" />
<SelectOption id="design" label="Design" />
<SelectOption id="finance" label="Finance" isDisabled />
<SelectOption id="people" label="People Operations" />
<SelectOption id="sales" label="Sales" />
</>
);
}
export function MultipleSelection() {
return (
<Select
label="Teams"
placeholder="Select teams"
selectionMode="multiple"
defaultValue={["eng", "design"]}
description="Members of these teams get the announcement."
>
<Departments />
</Select>
);
}
Controlled
Controlled value: value and onChange.
import { useState } from "react";
import { type Key } from "react-aria-components";
import { Select, SelectOption } from "@nexera-ui/react";
function Departments() {
return (
<>
<SelectOption id="eng" label="Engineering" />
<SelectOption id="design" label="Design" />
<SelectOption id="finance" label="Finance" isDisabled />
<SelectOption id="people" label="People Operations" />
<SelectOption id="sales" label="Sales" />
</>
);
}
export function Controlled() {
function Example() {
const [value, setValue] = useState<Key | null>("people");
return (
<div className="flex flex-col gap-3">
<Select label="Department" value={value} onChange={setValue}>
<Departments />
</Select>
<span className="text-body-small text-secondary">Value: {String(value)}</span>
</div>
);
}
return <Example />;
}
Platforms
Figma Select Mobile: platform="ios" (50 pt, up-down chevron) and platform="android" (52 dp), Body/Large, radius 12, 20 px icons; the options use Select option Mobile rows (44 pt / 48 dp).
import { Select, SelectOption } from "@nexera-ui/react";
import { LuCalendarDays } from "react-icons/lu";
const platforms = ["web", "ios", "android"] as const;
function LeaveTypes() {
return (
<>
<SelectOption id="annual" label="Annual leave" description="12 days available" />
<SelectOption id="sick" label="Sick leave" />
<SelectOption id="unpaid" label="Unpaid leave" />
</>
);
}
export function Platforms() {
return (
<div className="flex flex-wrap items-start gap-8">
{platforms.map((platform) => (
<div key={platform} className="w-80 max-w-full">
<Select
platform={platform}
label={`Leave type (${platform})`}
placeholder="Choose leave type"
description="12 days available"
{...(platform === "web" ? {} : { leadingIcon: <LuCalendarDays /> })}
>
<LeaveTypes />
</Select>
</div>
))}
</div>
);
}
Long label
Long labels, helper texts and values wrap.
import { Select, SelectOption } from "@nexera-ui/react";
export function LongLabel() {
return (
<div className="max-w-[14rem]">
<Select
label="Department responsible for approving travel and expense reports"
description="The approver is notified by email and in the app within five minutes."
defaultValue="rnd"
>
<SelectOption id="rnd" label="Research, development and quality assurance for mobile" />
<SelectOption id="ops" label="Operations" />
</Select>
</div>
);
}
Right to left
Right-to-left: label, icons, padding and the list mirror (logical properties); the list follows the direction.
import { Select, SelectOption } from "@nexera-ui/react";
import { LuBuilding2 } from "react-icons/lu";
export function RightToLeft() {
return (
<div className="flex flex-col gap-6">
<Select
label="القسم"
placeholder="اختر القسم"
description="يُستخدم للموافقات والتقارير."
leadingIcon={<LuBuilding2 />}
>
<SelectOption id="eng" label="الهندسة" />
<SelectOption id="design" label="التصميم" />
</Select>
<Select label="نوع الإجازة" platform="ios" isInvalid errorMessage="اختر نوع الإجازة.">
<SelectOption id="annual" label="إجازة سنوية" />
</Select>
</div>
);
}
Composition
Composable parts: SelectRoot + FieldLabel + SelectTrigger + SelectPopover, with a dynamic collection.
import {
FieldLabel,
SelectOption,
SelectPopover,
SelectRoot,
SelectTrigger,
} from "@nexera-ui/react";
export function Composition() {
const people = [
{ id: "omar", name: "Omar Farooq" },
{ id: "sara", name: "Sara Khan" },
{ id: "ali", name: "Ali Raza" },
];
return (
<SelectRoot size="md" defaultValue="sara">
<FieldLabel>Approver</FieldLabel>
<SelectTrigger />
<SelectPopover items={people} placement="top start">
{(person) => <SelectOption id={person.id} label={person.name} />}
</SelectPopover>
</SelectRoot>
);
}
Dependent locations
import { useState } from "react";
import { Card, Select, SelectOption } from "@nexera-ui/react";
function DependentLocationsDemo() {
const locations = {
pakistan: ["Karachi", "Lahore", "Islamabad"],
uae: ["Dubai", "Abu Dhabi", "Sharjah"],
germany: ["Berlin", "Hamburg", "Munich"],
};
const [country, setCountry] = useState<keyof typeof locations>("pakistan");
const [city, setCity] = useState("Karachi");
return (
<div className="w-[28rem] max-w-full">
<Card title="Find your next workspace" body="Country and city selectors stay in sync.">
<div className="mt-4 flex flex-col gap-4">
<Select
label="Country"
value={country}
onChange={(key) => {
if (key === "pakistan" || key === "uae" || key === "germany") {
setCountry(key);
setCity(locations[key][0] ?? "");
}
}}
>
<SelectOption id="pakistan" label="Pakistan" />
<SelectOption id="uae" label="United Arab Emirates" />
<SelectOption id="germany" label="Germany" />
</Select>
<Select
label="City"
value={city}
onChange={(key) => {
if (key !== null) setCity(String(key));
}}
>
{locations[country].map((name) => (
<SelectOption key={name} id={name} label={name} />
))}
</Select>
<p
role="status"
className="m-0 rounded-lg bg-brand-subtle p-3 text-body-default text-primary"
>
Showing workspaces in {city}.
</p>
</div>
</Card>
</div>
);
}
const DependentLocations = { render: () => <DependentLocationsDemo /> };
Props 39
Press "Try it" on a card to load that prop into the playground.
39 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.
itemsNexeraIterable<T>Item objects for a dynamic collection; children must then be a function.
descriptionNexeraReactNodeHelper text under the trigger. 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 trigger, 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.
leadingIconNexeraReactNodeIcon before the value. Decorative.
renderEmptyStateNexera((props: ListBoxRenderProps) => ReactNode)Content shown in the list when it has no options; the list only opens empty with allowsEmptyCollection.
selectedTagsLabelNexerastringAccessible name of the selected tags of a multi-select without a visible label. Translate it for your locale.
classNameNexerastringExtra classes for the field (label, trigger, helper text), merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the field.
sizeNexera"sm" | "md" | "lg"Height, padding and type of the trigger: 32 / 40 / 48 px with Body/Small, Body/Default and
Body/Large on web. Select Mobile has one size: 50 pt on iOS and 52 dp on Android with Body/Large, whatever size.
platformNexera"web" | "ios" | "android"Platform look. Defaults to the
NexeraProvider platform. The trigger, the list and its options follow it.
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.
autoFocusReact AriabooleanWhether the element should receive focus on render.
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).
nameReact AriastringThe name of the input, used when submitting an HTML form.
valueReact AriaKey | readonly Key[] | nullThe current value (controlled).
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.
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.
autoCompleteReact AriastringDescribes the type of autocomplete functionality the input should provide if any. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautocomplete).
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
isOpenReact AriabooleanSets the open state of the menu.
defaultOpenReact AriabooleanSets the default open state of the menu.
onOpenChangeReact Aria(isOpen: boolean) => voidMethod that is called when the open state of the menu changes.
shouldCloseOnSelectReact AriabooleanWhether the Select should close when an item is selected. Defaults to true if selectionMode is single, false otherwise.
allowsEmptyCollectionReact AriabooleanWhether the select should be allowed to be open when the collection is empty.
disabledKeysReact AriaIterable<Key>The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
defaultValueReact AriaKey | readonly Key[] | nullThe default value (uncontrolled).
onChangeReact Aria(value: ChangeValueType<M>) => voidHandler that is called when the value changes.
isRequiredReact AriabooleanWhether user input is required on the input before form submission.
isInvalidReact AriabooleanWhether the input value is invalid.
validateReact Aria(value: ValidationType<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.
placeholderReact AriastringTemporary text that occupies the select when it is empty.
* 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
7 direct · 7 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.10ReflowLevel 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
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-opendata-placeholderdata-pressed
<Select className="data-disabled:opacity-90 shadow-sm" />Used in blocks
- Onboarding stepsOnboarding
- Profile setupOnboarding
- Settings layoutApp shells and navigation
- Workspace switcherApp shells and navigation
- Budget varianceDashboards and analytics
- Skills comparisonDashboards and analytics
- Filters drawerLists, tables and records
- Contact formForms and data entry
- Add a personForms and data entry
- Multi-step formForms and data entry
- Address and phoneForms and data entry
- Date range pickerForms and data entry
- Team members and rolesSettings and preferences
- Leave requestScheduling and calendar
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.