Select
A single-choice list box with a button trigger, groups and separators, backed by a hidden native select so forms submit and reset as usual.
Import
import { Select } from '@nizivo/react/select';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/field.css and @nizivo/react/css/popover.css
import '@nizivo/react/css/select.css';Static or interactive
Client. It always ships React Aria's listbox and popup code and FieldScope (+72,949 B gzip under Turbopack for one Select on the Next.js fixture). NativeSelect is the static alternative when the browser's own picker fits. How to choose.
In a Field
Inside a Field, Select reads the Field the way Input does: the label and the selected value name
the trigger, the description and the error describe it, and the Field's name, required,
disabled and error state apply unless the Select sets its own (an explicit false wins). The
Field's name reaches the hidden native <select>, so the value is in FormData.
A click on the label focuses the trigger, with its focus ring, and never opens the list: a native
<label for> a button would press it. A required, empty Select stops the submit and takes focus,
but shows no inline message yet (React Aria handles the hidden <select>'s invalid event), so pass
error for it. The trigger is a button, which cannot carry aria-required, and the Field's * is
hidden from screen readers, so put "required" in the label or the description too (the maintainer's
rule for 0.1.0). A form reset puts
the default value back in the trigger and in the submitted data.
The selected row
The selected row keeps a --nz-bg-brand-subtle tint, and its label turns Label/M Strong in
--nz-text-primary. A check at its end (--nz-icon-brand) carries the state for readers who do not
see the colour (WCAG 1.4.1). The check's 16 px column is reserved on every row, so selecting never
moves the text. indicator on Select.Item replaces the check, and indicator={null} removes it.
Figma draws no tint on the selected row; the code keeps it (the maintainer's decision of
2026-10-18, listed under Deviations).
The list
The list follows Figma's Select Menu (7356:406): --nz-radius-md, --nz-elevation-4, 6 px inside,
and 4 px below the trigger (sideOffset on Select.Popup). Rows are 36 px of Label/M with 8 / 12 px
padding and no gap between them, and the keyboard ring is drawn inside the row. Group labels are
Label/S in --nz-text-secondary, padded 8 / 12 / 4 / 12. A separator has 4 px above and below. The
Dropdown Menu (Menu) keeps its own look.
Text size on touch screens
Under a coarse pointer (a phone or a tablet), the trigger's value and placeholder are 16 px (Label/L) instead of 14 px (Label/M) at every size, matching NativeSelect, so swapping one for the other does not change the text. The trigger keeps its 32, 40 or 48 px height. The list's rows stay at 14 px and its group labels at 12 px: they are not text fields, so iOS does not zoom into them.
Examples
Basic
import type { JSX } from 'react';
import { Select } from '@nizivo/react/select';
export function SelectExample(): JSX.Element {
return (
<Select.Root defaultValue="30d" name="period" aria-label="Period">
<Select.Trigger />
<Select.Popup>
<Select.Group>
<Select.GroupLabel>Recent</Select.GroupLabel>
<Select.Item value="7d">Last 7 days</Select.Item>
<Select.Item value="30d">Last 30 days</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Item value="all" disabled>
All time
</Select.Item>
</Select.Popup>
</Select.Root>
);
}
Field
In a Field: the label and description name and describe the trigger, the Field's name reaches the hidden native <select>, and a click on the label focuses the trigger (ring shown) without opening the list. The selected row shows the check (indicator replaces it; null removes it).
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Select } from '@nizivo/react/select';
export function SelectFieldExample(): JSX.Element {
return (
<Field
label="Reporting period"
description="Applies to every chart on this page."
name="period"
>
<Select.Root defaultValue="30d">
<Select.Trigger />
<Select.Popup>
<Select.Item value="7d">Last 7 days</Select.Item>
<Select.Item value="30d">Last 30 days</Select.Item>
<Select.Item value="90d">Last 90 days</Select.Item>
</Select.Popup>
</Select.Root>
</Field>
);
}
Open
Opened on the server: the popup still renders nothing until hydration (§12.2 SSR).
Source only
defaultOpen), which would move focus on this page, so it is shown as source only.import type { JSX } from 'react';
import { Select } from '@nizivo/react/select';
export function SelectOpenExample(): JSX.Element {
return (
<Select.Root defaultOpen placeholder="Choose a period" aria-label="Period" size="sm" invalid>
<Select.Trigger />
<Select.Popup>
<Select.Item value="7d">Last 7 days</Select.Item>
</Select.Popup>
</Select.Root>
);
}
Parts
Use the parts through the namespace (Select.Root) or by their flat names (SelectRoot); both work from Server Components.
Select.Root
Client component Renders data-slot="select" with the class nz-select. Props: SelectRootProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
aria-label | string | – | |
aria-labelledby | string | – | Names the trigger together with the value. Inside a Field, the Field label id goes here (§16.1). |
childrenrequired | ReactNode | – | |
className | string | – | |
defaultOpen | boolean | – | |
defaultValue | string | null | – | |
disabled | boolean | – | |
invalid | boolean | – | An error message is presented (owned by Field, never by the engine). Inside a Field, the Field sets it. |
name | string | – | With |
onOpenChange | (open: boolean, details: ChangeDetails<OpenReason>) => void | – | |
onValueChange | (value: string | null, details: ChangeDetails<SelectValueReason>) => void | – |
|
open | boolean | – | |
placeholder | SelectLabels["placeholder"] | – | |
required | boolean | – | |
size | ControlSize"sm""md""lg" | 'md' | 32 / 40 / 48 px. |
value | string | null | – |
Select.Trigger
Client component Renders data-slot="select-trigger" with the class nz-select-trigger. Props: SelectTriggerProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | – | Replaces the default |
indicator | ReactNode | – | Replaces the chevron glyph. |
leadingIcon | ReactNode | – | |
render | RenderProp<SelectTriggerState> | – | Must render a |
- Also takes every attribute of
<button>exceptchildren,disabled,type. (Omit<ComponentPropsWithRef<"button">, "children" | "disabled" | "type">)
render takes an element, or, in client code, a function of the props and this state (SelectTriggerState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
focusVisiblerequired | boolean | – | |
openrequired | boolean | – | |
pressedrequired | boolean | – |
Select.Value
Client component Renders data-slot="select-value" with the class nz-select-value. Props: SelectValueProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<SelectValueState> | – | Must render a |
- Also takes every attribute of
<span>exceptchildren. (Omit<ComponentPropsWithRef<"span">, "children">)
render takes an element, or, in client code, a function of the props and this state (SelectValueState):
| Name | Type | Default | Description |
|---|---|---|---|
placeholderrequired | boolean | – |
Select.Popup
Client component Renders data-slot="select-popup" with the class nz-select-popup. Props: SelectPopupProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
align | Align"start""center""end" | 'start' | |
childrenrequired | ReactNode | – | |
className | string | – | |
container | Element | null | – | Portal container. Defaults to the parent overlay, else |
side | "top" | "bottom" | 'bottom' | |
sideOffset | number | 4 | Distance from the trigger in px. |
style | CSSProperties | – |
Select.Item
Client component Renders data-slot="select-item" with the class nz-select-item. Props: SelectItemProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | – | |
disabled | boolean | – | |
indicator | ReactNode | – | Replaces the check shown at the end of the selected item; |
render | RenderProp<SelectItemState> | – | Must render a |
textValue | string | – | Typeahead text; defaults to |
valuerequired | string | – |
- Also takes every attribute of
<div>exceptchildren,id. (Omit<ComponentPropsWithRef<"div">, "children" | "id">)
render takes an element, or, in client code, a function of the props and this state (SelectItemState):
| Name | Type | Default | Description |
|---|---|---|---|
disabledrequired | boolean | – | |
focusVisiblerequired | boolean | – | |
highlightedrequired | boolean | – | |
selectedrequired | boolean | – |
Select.Group
Client component Renders data-slot="select-group" with the class nz-select-group. Props: SelectGroupProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<NoState> | – | Must render a |
- Also takes every attribute of
<section>exceptid. (Omit<ComponentPropsWithRef<"section">, "id">)
render takes an element, or, in client code, a function of the props and no state.
Select.GroupLabel
Client component Renders data-slot="select-group-label" with the class nz-select-group-label. Props: SelectGroupLabelProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<NoState> | – | Must render a |
- Also takes every attribute of
<header>exceptid. (Omit<ComponentPropsWithRef<"header">, "id">)
render takes an element, or, in client code, a function of the props and no state.
Select.Separator
Client component Renders data-slot="select-separator" with the class nz-select-separator. Props: SelectSeparatorProps. Source: src/select/select.client.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
render | RenderProp<NoState> | – | Must render a |
- Also takes every attribute of
<div>exceptchildren. (Omit<ComponentPropsWithRef<"div">, "children">)
render takes an element, or, in client code, a function of the props and no state.
Other exports
SelectGroupLabelPropstypeimport type { SelectGroupLabelProps } from '@nizivo/react/select'The props of
Select.GroupLabel, listed above.SelectGroupPropstypeimport type { SelectGroupProps } from '@nizivo/react/select'The props of
Select.Group, listed above.SelectItemPropstypeimport type { SelectItemProps } from '@nizivo/react/select'The props of
Select.Item, listed above.SelectItemStatetypeimport type { SelectItemState } from '@nizivo/react/select'The render state of
Select.Item, listed above.SelectLabelstypeimport type { SelectLabels } from '@nizivo/react/select'Strings the engine renders (§9.3 rule 7).
placeholderis passed to the engine. Engine-owned with React Aria (not overridable): the visually hidden "Dismiss" buttons in the popup.Name Type Default Description placeholderrequiredstring– Shown while no value is chosen. React Aria's default is its localized "Select an item".
SelectPopupPropstypeimport type { SelectPopupProps } from '@nizivo/react/select'The props of
Select.Popup, listed above.SelectPopupStatetypeimport type { SelectPopupState } from '@nizivo/react/select'Name Type Default Description enteringrequiredboolean– exitingrequiredboolean– siderequiredSide"top""right""bottom""left"– SelectRootPropstypeimport type { SelectRootProps } from '@nizivo/react/select'The props of
Select.Root, listed above.SelectRootStatetypeimport type { SelectRootState } from '@nizivo/react/select'Name Type Default Description disabledrequiredboolean– openrequiredboolean– SelectSeparatorPropstypeimport type { SelectSeparatorProps } from '@nizivo/react/select'The props of
Select.Separator, listed above.SelectTriggerPropstypeimport type { SelectTriggerProps } from '@nizivo/react/select'The props of
Select.Trigger, listed above.SelectTriggerStatetypeimport type { SelectTriggerState } from '@nizivo/react/select'The render state of
Select.Trigger, listed above.SelectValuePropstypeimport type { SelectValueProps } from '@nizivo/react/select'The props of
Select.Value, listed above.SelectValueReasontypeimport type { SelectValueReason } from '@nizivo/react/select'type SelectValueReason = "item-press" | "keyboard" | "typeahead" | "form-reset"SelectValueStatetypeimport type { SelectValueState } from '@nizivo/react/select'The render state of
Select.Value, listed above.
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
select.nz-select | Select.Rootroot (no render) |
|
select-chevron.nz-select-chevron | Select.Triggerinner element (CSS only) | none |
select-group.nz-select-group | Select.Grouproot (takes render) | none |
select-group-label.nz-select-group-label | Select.GroupLabelroot (takes render) | none |
select-icon.nz-select-icon | Select.Triggerinner element (CSS only) | none |
select-item.nz-select-item | Select.Itemroot (takes render); interactive, focus: engine; forced colours: highlight on [data-selected] |
|
select-list.nz-select-list | Select.Popupinner element (CSS only) | none |
select-popup.nz-select-popup | Select.Popuproot (no render); forced colours: border |
|
select-separator.nz-select-separator | Select.Separatorroot (takes render) | none |
select-trigger.nz-select-trigger | Select.Triggerroot (takes render); interactive, focus: self |
|
select-value.nz-select-value | Select.Valueroot (takes render) |
|
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-select-bg | --nz-bg-surface |
|
--nz-select-border |
|
|
--nz-select-border-hover |
|
|
--nz-select-height |
|
|
--nz-select-radius |
|
|
Layers: nz.components. Classes: .nz-select-chevron, .nz-select-group, .nz-select-group-label, .nz-select-icon, .nz-select-item, .nz-select-item-indicator, .nz-select-list, .nz-select-popup, .nz-select-separator, .nz-select-trigger, .nz-select-value. Runtime variables: --nz-available-height, --nz-transform-origin, --nz-trigger-width.
Tokens this stylesheet reads (44)
--nz-bg-brand-subtle, --nz-bg-disabled, --nz-bg-hover, --nz-bg-surface, --nz-bg-surface-raised, --nz-border-control, --nz-border-control-hover, --nz-border-critical, --nz-border-subtle, --nz-border-width-default, --nz-border-width-focus, --nz-duration-fade, --nz-duration-fast, --nz-duration-spring, --nz-easing-enter, --nz-easing-exit, --nz-easing-spring, --nz-easing-standard, --nz-elevation-4, --nz-focus-halo, --nz-focus-halo-critical, --nz-font-label-l, --nz-font-label-m, --nz-font-label-m-strong, --nz-font-label-s, --nz-icon-brand, --nz-radius-md, --nz-radius-sm, --nz-scale-enter, --nz-size-control-lg, --nz-size-control-md, --nz-size-control-sm, --nz-size-icon-sm, --nz-space-1, --nz-space-1-5, --nz-space-2, --nz-space-3, --nz-text-disabled, --nz-text-primary, --nz-text-secondary, --nz-text-tertiary, --nz-tracking-label-m, --nz-tracking-label-s, --nz-z-overlay
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
select-item: draws the focus outline when the engine reports keyboard focus, not on pointer hover.select-trigger: draws the focus outline itself on keyboard focus.
In forced-colours mode these states keep a visible system colour: select-item[data-selected] (highlight), select-popup (border).
Behaviour comes from the react-aria engine through Nizivo's adapter: focus management, ARIA wiring and keyboard handling are the engine's.
Pattern: WAI-ARIA Authoring Practices.
Keyboard
| Keys | Action |
|---|---|
| Enter, Space, ArrowDown | On the trigger, opens the list and focuses the selected option, else the first. |
| ArrowUp | On the trigger, opens the list and focuses the selected option, else the last. |
| ArrowLeft, ArrowRight | On the closed trigger, selects the previous or next option without opening the list. |
| Type a character | Selects (on the trigger) or focuses (in the list) the next option whose label starts with what you typed. |
| ArrowDown, ArrowUp | In the open list, moves focus to the next or previous option, skipping disabled ones. |
| Home, End, PageUp, PageDown | In the open list, moves focus to the first or last option, or a page at a time. |
| Enter, Space | In the open list, selects the focused option and closes the list. |
| Escape | Closes the list without changing the selection and returns focus to the trigger. |
| Tab, Shift+Tab | In the open list, closes it without changing the selection and moves focus to the next or previous element after the trigger. |
Source: react-aria-components 1.22.1 Select (react-aria 3.53.1 useSelect, useMenuTrigger and useSelectableCollection) through _engines/react-aria/select.client.tsx. apps/docs/scripts/smoke.ts checks ArrowDown opening on the selected option, ArrowDown and Enter choosing, Escape closing with focus back on the trigger, ArrowRight and ArrowLeft on the closed trigger, and typing on the trigger; the overlay lane (test/overlay/select.spec.tsx) checks Tab and Shift+Tab, which the adapter handles because React Aria keeps Tab inside its modal popover.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: Figma node
7275:11438, Figma node7356:401, Figma node7356:402, Figma node7356:404, Figma node7356:406 - Ghost masters: Figma node
745:222
SegmentedControl
A compact single choice between a few options, such as a chart's time range. Native radios inside labels, so it submits, resets and works before hydration. Inside a Field, give the Field group.
Separator
An <hr>: a 1 px border/subtle line, Figma's Divider (7370:95). Decorative by default; spacing comes first, a line only when spacing is not enough (the Figma doc).