Skip to content
Nizivo UI

NativeSelect

The browser's own select, styled like the Select trigger. Server-safe with no client code; use it where the native picker is the better choice, such as on phones or in Server Component forms.

Server component, no client JS

Import

import { NativeSelect } from '@nizivo/react/native-select';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css
import '@nizivo/react/css/native-select.css';

Placed directly inside a Field, or through a function child, a NativeSelect is wired as plain attributes with no client code. It keeps the browser's own validation bubble rather than the Field's inline message, so do not mix it with Field-wired client controls in one native-validated form (see Field).

Static or interactive

Server. It ships no client JavaScript when a Server Component renders it, in every Next.js build mode; its code is bundled only when a client component (a 'use client' file) imports it. It is the static alternative to Select when the browser's own picker fits. Inside a Field (placed directly or through a function child) it is wired with no client code, but Turbopack and next dev still ship the Field's FieldScope module; FieldStatic ships none. How to choose.

In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.

Text size on touch screens

Under a coarse pointer (a phone or a tablet), the value is 16 px (Label/L) instead of 14 px (Label/M) at every size, as on the Select trigger, because iOS Safari zooms the page into a focused <select> whose text is smaller than 16 px. The box keeps its 32, 40 or 48 px height. --nz-native-select-font replaces the font on every pointer, so a page that sets it also decides whether iOS zooms.

Examples

Basic

Zero JS: placed in a Field, the select gets its id, name and ARIA wiring as plain attributes (a function child, {(control) => <NativeSelect {...control} />}, does the same). The page ships no client code for it, and the browser validates required itself.

Where the order ships.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { NativeSelect } from '@nizivo/react/native-select';

export function NativeSelectExample(): JSX.Element {
  return (
    <Field label="Country" description="Where the order ships." required>
      <NativeSelect name="country" placeholder="Choose a country">
        <option value="IN">India</option>
        <option value="GB">United Kingdom</option>
        <option value="US">United States</option>
      </NativeSelect>
    </Field>
  );
}

Groups

Option groups, as a native <select> draws them.

import type { JSX } from 'react';
import { NativeSelect } from '@nizivo/react/native-select';

export function NativeSelectGroupsExample(): JSX.Element {
  return (
    <NativeSelect aria-label="Period" defaultValue="30d">
      <optgroup label="Recent">
        <option value="7d">Last 7 days</option>
        <option value="30d">Last 30 days</option>
      </optgroup>
      <optgroup label="Longer">
        <option value="12m">Last 12 months</option>
        <option value="all">All time</option>
      </optgroup>
    </NativeSelect>
  );
}

Sizes

32, 40 and 48 px: the Select trigger's heights, so the two can swap without a layout shift.

import type { JSX } from 'react';
import { NativeSelect } from '@nizivo/react/native-select';

export function NativeSelectSizesExample(): JSX.Element {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 }}>
      <NativeSelect size="sm" aria-label="Small" defaultValue="a">
        <option value="a">Small</option>
      </NativeSelect>
      <NativeSelect aria-label="Medium" defaultValue="a">
        <option value="a">Medium</option>
      </NativeSelect>
      <NativeSelect size="lg" aria-label="Large" defaultValue="a">
        <option value="a">Large</option>
      </NativeSelect>
    </div>
  );
}

States

An error from the server, and disabled.

Choose a zone that ships to this address.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { NativeSelect } from '@nizivo/react/native-select';

export function NativeSelectStatesExample(): JSX.Element {
  return (
    <div style={{ display: 'grid', gap: 16, maxInlineSize: 320 }}>
      <Field label="Shipping zone" error="Choose a zone that ships to this address.">
        {(control) => (
          <NativeSelect {...control} defaultValue="eu">
            <option value="eu">Europe</option>
            <option value="asia">Asia</option>
          </NativeSelect>
        )}
      </Field>
      <Field label="Currency" disabled>
        {(control) => (
          <NativeSelect {...control} defaultValue="INR">
            <option value="INR">Indian rupee</option>
          </NativeSelect>
        )}
      </Field>
    </div>
  );
}

Props

Server component Renders data-slot="native-select" with the class nz-native-select. Props: NativeSelectProps. Source: src/native-select/native-select.tsx.

NameTypeDefaultDescription
childrenReactNode–

<option> and <optgroup> elements.

indicatorReactNode–

Replaces the chevron glyph. Decorative.

invalidboolean–

An error message is presented for this select (§8.3). A Field's function child passes aria-invalid, which counts too.

leadingIconReactNode–

Decorative icon before the value (aria-hidden), as on the Select trigger.

placeholderstring–

Text of a first, empty option shown until a value is chosen (value=""). With required, submitting it fails valueMissing. When neither value nor defaultValue is given, it is selected initially.

readOnlyboolean–

readOnly is accepted and dropped, so a Field function child can be spread as it is: a <select> has no read-only state.

renderRenderProp<NativeSelectState>–

Composition of the wrapper. className and style go on the wrapper too; every other prop, ref included, goes on the native <select>.

sizeControlSize
"sm""md""lg"
'md'

32 / 40 / 48 px, the Select trigger's heights.

  • Also takes every attribute of <select> except multiple, readOnly, size; children is redeclared above. (Omit<ComponentPropsWithRef<"select">, "size" | "multiple" | "readOnly">)

render takes an element, or, in client code, a function of the props and this state (NativeSelectState):

NameTypeDefaultDescription
disabledrequiredboolean–
invalidrequiredboolean–

An error message is presented: the invalid prop, or aria-invalid from a Field.

sizerequiredControlSize
"sm""md""lg"
–

Other exports

NativeSelectProps type

import type { NativeSelectProps } from '@nizivo/react/native-select'

The props of NativeSelect, listed above.

NativeSelectState type

import type { NativeSelectState } from '@nizivo/react/native-select'

The render state of NativeSelect, listed above.

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
native-select
.nz-native-select
NativeSelect
root (takes render)
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-invalid (present or absent) ← invalid
    An error message is currently presented (not "fails constraints"); always the Field's own state, never the engine's
  • data-size: "sm""md""lg" ← size
    The size prop; values in each component manifest
native-select-chevron
.nz-native-select-chevron
NativeSelect
inner element (CSS only)
none
native-select-icon
.nz-native-select-icon
NativeSelect
inner element (CSS only)
none
native-select-trigger
.nz-native-select-trigger
NativeSelect
inner element (CSS only); interactive, focus: self
none

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.

HookDefaultRead as
--nz-native-select-bg
  • default: var(--nz-bg-surface)
  • default: var(--nz-bg-disabled)
  • background on .nz-native-select-trigger
--nz-native-select-border
  • default: var(--nz-border-control)
  • default: var(--nz-border-subtle)
  • data-invalid: var(--nz-border-critical)
  • border on .nz-native-select-trigger
--nz-native-select-font
  • default: var(--nz-font-label-m)
  • default @media (pointer: coarse): var(--nz-font-label-l)
  • font on .nz-native-select-trigger
--nz-native-select-height
  • default: var(--nz-size-control-md)
  • data-size="lg": var(--nz-size-control-lg)
  • data-size="sm": var(--nz-size-control-sm)
  • block-size on .nz-native-select-trigger
--nz-native-select-icon-fg--nz-text-secondary
  • color on .nz-native-select-chevron
  • color on .nz-native-select-icon
--nz-native-select-radius
  • default: var(--nz-radius-md)
  • data-size="sm": var(--nz-radius-sm)
  • border-radius on .nz-native-select-trigger

Layers: nz.components. Classes: .nz-native-select, .nz-native-select-chevron, .nz-native-select-icon, .nz-native-select-trigger.

Tokens this stylesheet reads (26)

--nz-bg-disabled, --nz-bg-surface, --nz-border-control, --nz-border-control-hover, --nz-border-critical, --nz-border-subtle, --nz-border-width-default, --nz-border-width-focus, --nz-duration-fast, --nz-easing-standard, --nz-focus-halo, --nz-focus-halo-critical, --nz-font-label-l, --nz-font-label-m, --nz-radius-md, --nz-radius-sm, --nz-size-control-lg, --nz-size-control-md, --nz-size-control-sm, --nz-size-icon-sm, --nz-space-2, --nz-space-3, --nz-text-disabled, --nz-text-primary, --nz-text-secondary, --nz-text-tertiary

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:

  • native-select-trigger: draws the focus outline itself on keyboard focus.

Keyboard

KeysAction
TabMoves focus to the select.
ArrowUp, ArrowDown, Space, Enter, Type a characterThe browser's own select keyboard, which differs by platform; typing jumps to the next option that starts with what you typed.

Source: a native <select> (native-select.tsx), which adds no key handling.

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: none
  • Ghost masters: none

On this page