Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Native Select

The platform select, tokenized — the OS picker on mobile, option groups, and the same chrome as every other field.

Status
stable
Since
0.10.0
Accessibility pattern
native select

Last updated

Install

Add Native Select from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/native-select

Usage

import {
  NativeSelect,
  NativeSelectOptGroup,
  NativeSelectOption,
} from "@/components/ui/native-select";

<NativeSelect>
  <NativeSelectOption value="">Select a fruit</NativeSelectOption>
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
</NativeSelect>;

Anatomy

NativeSelect — data-slot="native-select" | "native-select-icon" | "native-select-wrapper"
NativeSelectOption — data-slot="native-select-option"
NativeSelectOptGroup — data-slot="native-select-optgroup"

Examples

Composition

Options sit directly under NativeSelect, or inside a NativeSelectOptGroup.

NativeSelect
├── NativeSelectOption
└── NativeSelectOptGroup
    ├── NativeSelectOption
    └── NativeSelectOption

Groups

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 tint is scoped to not-focus: (FOC-5), so a focused select shows its focus cue rather than the error colour.

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

PropTypeDefaultDescription
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-hidden and pointer-events-none; the select itself owns the whole box.
  • Focus shows the border tint on :focus (FOC-3) and the global 2px outline, because this is a button-style trigger rather than a text field (FOC-4).
  • Give it a label: FieldLabel bound with htmlFor, or an aria-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.
ContractStates tested
Behaviourdefault, disabled, invalid
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test, invalid, disabled
Visualdefault, focus, invalid, disabled

Do / Don't

Do
Reach for NativeSelect first on mobile and in dense tables — the platform picker is faster and more familiar.
Don't
Use NativeSelect when an option needs an icon, a description or any markup — that is Select.

Deviations

Upstream's file plus packages/ui/upstream/patches/native-select.patch. Every hunk:

  • FOC-1, FOC-3, FOC-4, FOC-6outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 becomes focus:border-ring/70: the border tint fires on :focus for mouse and keyboard alike, and the outline-none is dropped so the global :focus-visible outline still paints. A native select is a button-style trigger, so it keeps the outline — unlike Input and Textarea, which suppress it.
  • FOC-5 — the invalid tint becomes not-focus:aria-invalid:border-destructive (and its dark counterpart), so focus outranks it; the aria-invalid halo rings are removed with the rest.
  • FRM-4disabled:pointer-events-none is dropped, so a disabled select stays hoverable and a Tooltip can explain it.
  • DOC-2cn is imported from @vegastack/design.

On this page