Skip to content

Native Select

Stable

The browser's own select element, styled to match prfct — best on mobile, in long forms and wherever zero JavaScript matters.

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/native-select.json

Usage

import {
  NativeSelect,
  NativeSelectOptGroup,
  NativeSelectOption,
} from "@/components/ui/native-select"
<NativeSelect defaultValue="yearly">
  <NativeSelectOption value="monthly">Monthly</NativeSelectOption>
  <NativeSelectOption value="yearly">Yearly</NativeSelectOption>
</NativeSelect>

className styles the wrapper, which is w-fit by default — pass a width such as w-full to size the control. Every other prop goes to the <select> element.

Examples

Sizes

sm, default and lg match the heights of Input and Button, so a filter and its action line up.

Option groups

NativeSelectOptGroup adds non-selectable headings to long lists. Operating systems render them natively — indented on desktop, sectioned in mobile pickers.

Invalid and disabled

A disabled, empty first option works as a placeholder: combined with required, the browser refuses to submit until a real choice is made. Mark invalid selections with aria-invalid and a FieldError, exactly as you would an input.

Guidelines

When to use

  • Mobile-heavy flows: phones open their platform picker, which is faster and familiar.
  • Long forms and server-rendered pages that must work before — or without — JavaScript.
  • Simple lists of plain-text options where custom rendering adds nothing.

When not to use

  • Options that need icons, descriptions or custom layout — use Select.
  • Lists long enough to search — use a Combobox.
  • Two to five options people should compare at a glance — use a Radio Group or Toggle Group.

Native or custom?

Native SelectSelect
MobilePlatform pickerprfct popup
Works before hydrationYesNo
Rich option contentText onlyIcons, descriptions, any JSX
Consistent look across platformsTrigger onlyTrigger and list
Typeahead and keyboardNativeImplemented by Base UI

Choose one per product surface and stay consistent; mixing both in one form makes it feel unfinished.

Accessibility

Native Select is a real <select>, so it inherits the platform's keyboard support, screen reader semantics and form behavior with no scripting.

  • Label it with a FieldLabel and htmlFor/id, or an aria-label for compact filters.
  • System colors. Options use the Canvas and CanvasText system colors, so the open list follows the operating system and the page's color-scheme in dark mode.
  • Contrast. The border uses the input token at 3:1 against the page; the chevron is decorative and hidden from assistive technology.
KeyBehavior
Tab
Moves focus to the select.
SpaceEnterAlt
Opens the list (platform-dependent).
Changes the selection — without opening the list on most desktop browsers.
A–Z
Jumps to the next option starting with that letter.

API reference

NativeSelect

Renders a wrapper <div> with a <select> and a chevron icon. Accepts all native select props.

PropTypeDefault
size

Height and padding: 32, 36 or 40px.

"sm" | "default" | "lg""default"
className

Applied to the wrapper. Use it for width and layout.

stringNo default
value

The controlled value. Pair with onChange.

stringNo default
defaultValue

The initial value of an uncontrolled select.

stringNo default
disabled

Disables the select and dims it.

booleanfalse
aria-invalid

Shows the invalid border and focus halo.

booleanNo default

NativeSelectOption

Renders an <option>. Accepts all native option props, including value and disabled.

NativeSelectOptGroup

Renders an <optgroup>. Set its label to the group heading.