Skip to content

Native select

Native select — the browser’s <select>, styled as far as CSS reaches: the box is Select’s trigger (Input’s chrome — 36px, radius 8, padding 8/12, 14px Regular, 1px border, elevation/flat — with the 16px chevron-down in text/muted), the list that opens is the browser’s and cannot be styled.

Beta · Forms · packages/css/src/components/native-select.css · React on Base UI: @pepperui/native-select

.pui-native-select wraps a real <select>; the chevron is a mask in text/muted. Never on a product screen.

Native select — every size and every stateOpen in the workshop

Sizes across, states down.

  • Placeholder and Value — a placeholder is the disabled first option, selected until the user chooses
  • No prefix, no leading icon: a native control cannot host them (Select can)
  • Three sizes and five states, Select’s exactly — the box is the same component drawn twice so a form can be re-platformed without a visual change
  • Never used in a product screen — product screens take Select
  • Filled true — the box on surface/card instead of white, so a form on a white page can tell its dropdowns from its inputs: The design lead’s call on the site’s blog and careers filters, ‘for visibility’ (request 19)

Use when a form must work without JavaScript — a server-rendered admin page, a form embedded where scripts do not run, an email-safe page.

Do not use when the page runs the design system’s JavaScript (that is Select); the list needs search or typing (Combobox); the options are few enough to show (Radio); on any deck-tool or MAFO screen.

Relates to Select, Field, Combobox, Input, Radio.

Anti-patterns styling the open list (it cannot be — say so in the review, do not mock it) · a native select in a product screen · a placeholder that is not the disabled first option · the browser’s own arrow flush at the edge · a fill changed on the instance.

Keywords: native select, select, form, no-js, dropdown, browser

Figma — page Select: Native select (30 variants)

shadcn: native-select

The class, the parts, and nothing that is not in the package. The same block opens native-select.css.

<span class="pui-native-select"><select aria-label="Category"><option value="" disabled selected>Choose…</option>…</select></span>
What it is Note
Size --small · (none) medium · --large — on the wrapper, mirroring Select —
State the select’s own: :hover · :focus · [aria-invalid=“true”] (+ the error ring when focused) · :disabled —

Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.

ink on ground reads floor the matrix calls it
#544d4d on #ffffff 8.24 4.5 text.muted on surface.page (promised) ok
#0b0202 on #ffffff 20.51 4.5 text.default on surface.page (promised) ok

2 distinct pair(s) rendered, 0 that axe calls a violation.

primitive on the indicator over the change
Native select surface/page shadow #6a66e4 2 px #ffffff 4.53 ok
Native select surface/card shadow #6a66e4 2 px #f2f2f7 4.06 ok
Native select surface/sunken shadow #6a66e4 2 px #f2f2f2 4.05 ok

3 of 3 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.

Nothing in this component claims an ARIA role with a keyboard contract; its controls are ordinary ones and the browser already gives them their keys.

Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).

Pepper UI v1.5.0

The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.