Native Select
The platform select, tokenized — the OS picker on mobile, option groups, and the same chrome as every other field.
- Status
- Since
0.10.0- Accessibility pattern
- native select
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Native Select before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify native-selectInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/native-selectRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
Usage
import {
NativeSelect,
NativeSelectOptGroup,
NativeSelectOption,
} from "@/components/ui/native-select";
<NativeSelect>
<NativeSelectOption value="">Select a fruit</NativeSelectOption>
<NativeSelectOption value="apple">Apple</NativeSelectOption>
</NativeSelect>;Anatomy
Examples
Composition
Options sit directly under NativeSelect, or inside a NativeSelectOptGroup.
NativeSelect
├── NativeSelectOption
└── NativeSelectOptGroup
├── NativeSelectOption
└── NativeSelectOptionGroups
NativeSelectOptGroup organises options into categories the platform renders itself.
Disabled
Add disabled to the NativeSelect. The wrapper dims with has-[select:disabled].
Invalid
Use aria-invalid on the select and data-invalid on the Field. The destructive border
holds in every state, focused or not (FOC-14).
Native Select vs Select
- Use NativeSelect for native browser behaviour, better performance, or a mobile-optimised picker.
- Use Select for custom item markup, icons, descriptions, animation, or a scrollable grouped popup.
The platform picker — fastest, and the right control on mobile.
A rendered popup — icons, descriptions and animation.
RTL
The chevron is positioned with end-2.5 and the padding with ps-/pe-, so a right-to-left
document mirrors the whole control.
Sizes
size takes sm (28px) and default (32px).
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
size | "default" | "sm" | — |
NativeSelectOption and NativeSelectOptGroup add no props of their own — each accepts everything
the native <option> and <optgroup> accept.
Accessibility
- Renders a native
<select>, so the platform owns the popup, type-ahead, keyboard model and the mobile picker sheet. Nothing here re-implements any of it. - The chevron is
aria-hiddenandpointer-events-none; the select itself owns the whole box. - Focus shows
base.css's global background tint on:focus-visible, like every other trigger (FOC-4); the border never changes colour (FOC-14). - Give it a label:
FieldLabelbound withhtmlFor, or anaria-label. - Options set
bg-[Canvas] text-[CanvasText]so the popup stays legible under a forced-colours palette and in a dark scope, where some platforms would otherwise paint dark-on-dark.
| Contract | States tested |
|---|---|
| Behaviour | default, disabled, invalid |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test, invalid, disabled |
| Visual | default, focus, invalid, disabled |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/native-select.patch. Every hunk:
- FOC-1, FOC-3, FOC-4, FOC-6 —
outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50is removed outright,outline-noneincluded, so the global:focus-visibletint paints and the border staysborder-input(FOC-14 retired thefocus:border-ring/70tint). - FOC-14 — the invalid border is
aria-invalid:border-destructive(and its dark counterpart), held in every state, focused or not; thearia-invalidhalo rings are removed with the rest. - FRM-4 —
disabled:pointer-events-noneis dropped, so a disabled select stays hoverable and a Tooltip can explain it. - DOC-2 —
cnis imported from@vegastack/design. - COL-24, INT-4 — hover and press come from the shared
state-layerutility, a translucent foreground layer (--state-hover,--state-pressed) over whatever fill is there, so the step reads the same on every surface in both themes; selected, open and highlighted fills arebg- state-selected/bg-state-hover. Operator, Regent, 2026-10-31.