---
title: Native Select
description: The platform's own select in the site's clothes - the phone wheel and the OS menu, kept.
source: https://adamjurek.com/components/native-select
---

## Home


A native `<select>` restyled to match the site's fields, with a drawn-on
chevron replacing the one `appearance: none` removes. Reach for it for any
fixed set of options where the platform's own picker is welcome.

<!-- ::start:showcase demo="native-select" height="380" -->
<!-- ::end:showcase -->

## Why it is built this way

Opening it hands off entirely to the platform: the OS menu on desktop, the
wheel on iOS, the sheet Android draws for `<select>`. That handoff is the
whole argument for it over a listbox rebuilt in divs, and it is also why only
the closed state is this component's to style - the chevron replaces the one
`appearance: none` takes away along with the browser's own styling.

## What it does not do

It cannot show more than text in an option - no icon, no description, no
price next to a label - because the OS renders the popup and only reads the
option's text. Multiple selection and in-page search are out for the same
reason: there is no listbox here to replace a native `<select>` with.

> [!NOTE] Install commands are not written here
> Anything in `packages/ui/registry.ts` gets its TanStack and shadcn commands
> attached automatically, so there is nothing to keep in sync.


## Install

<!-- ::start:tabs -->

### TanStack

```shell
tanstack add https://adamjurek.com/r/tanstack/native-select.json
```

### shadcn

```shell
pnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/native-select.json
```

### pnpm

```shell
pnpm add @sushindustries/ui @sushindustries/atoms
```

<!-- ::end:tabs -->

## What you get

| | |
| --- | --- |
| Version | 0.1.0 |
| Category | content · Forms |
| Files | `native-select.tsx` |
| Dependencies | None |
| Also installs | `icon` |
| Tags | form, no-deps |

> [!NOTE] No runtime dependencies
> It brings nothing with it beyond the stylesheet.

## Get Started


Install commands are on Home, attached from the registry - they are not written
here, because a second copy is a copy that goes stale. This tab starts after the
install worked.

## Use it

```tsx
import { NativeSelect } from "@sushindustries/ui";

export function CountryField() {
	return (
		<NativeSelect name="country" defaultValue="pl">
			<option value="pl">Poland</option>
			<option value="de">Germany</option>
			<option value="fr">France</option>
		</NativeSelect>
	);
}
```

## What you should see

A field-styled control with a chevron on the right, holding whichever
`<option>` is selected. Opening it hands off entirely to the platform: the OS
picker on desktop, the wheel on iOS, the sheet Android draws for `<select>`.
None of that chrome is this component's to style - only the closed state is.

## If nothing happens

`NativeSelect` accepts every prop a `<select>` does, so a missing value
usually means missing `<option>` children - an empty select has nothing to
open. If the control renders but looks like the browser default rather than
the site's style, `@sushindustries/atoms/atoms.css` isn't imported at the
root.


## Guides


## Composing it

It renders a `<span className="select-wrap">` around the real `<select>`, so
treat it the way you'd treat an `<input>`: inside a `<label>`, a `.field`
wrapper, or a form grid cell. It needs no container with a fixed height - the
field sizes to its content like any inline control.

## When not to use it

When an option needs more than text - an icon, a description, a price next to
a label - because the OS renders the popup and only reads the option's text.
Same for anything needing multiple selections or an in-page search: a native
`<select>` cannot do either, and there is no listbox in this library built to
replace it.


## API


<!-- generated:api -->

## Props

Accepts every prop of `SelectHTMLAttributes<HTMLSelectElement>`.

<!-- /generated:api -->

## Notes

There are no props beyond what `<select>` already takes - `NativeSelect` is a
thin wrap, not a variant. `className` lands on the `<select>` itself, merged
after `field-control`; it never reaches the wrapping span or the chevron, so
a class meant to move either has nothing to select from outside.


## Examples


Examples are the tab where the component is shown doing a job, not
demonstrating a prop. The API tab already lists the props.

<!-- ::start:showcase demo="native-select" height="420" -->
<!-- ::end:showcase -->

Press Compare. The frames are real viewports, so a layout that breaks at 320
breaks here too rather than in somebody's hands.

## In a page

```tsx
import { NativeSelect } from "@sushindustries/ui";

export function ShippingForm() {
	return (
		<form className="flex flex-col gap-4">
			<label className="field">
				<span className="label">Country</span>
				<NativeSelect name="country" required defaultValue="">
					<option value="" disabled>
						Choose one
					</option>
					<option value="pl">Poland</option>
					<option value="de">Germany</option>
				</NativeSelect>
			</label>
			<label className="field">
				<span className="label">Size</span>
				<NativeSelect name="size" defaultValue="m">
					<option value="s">S</option>
					<option value="m">M</option>
					<option value="l">L</option>
				</NativeSelect>
			</label>
		</form>
	);
}
```
