# Native Select

A styled native <select> element with a custom chevron, size variants, and sound feedback.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).





<ComponentThemeTabs>
  <ThemeTab name="mint">
    <PreviewWithCode name="native-select">
      <NativeSelectDemo />
    </PreviewWithCode>
  </ThemeTab>

  <ThemeTab name="berries">
    <PreviewWithCode name="native-select">
      <NativeSelectDemo />
    </PreviewWithCode>
  </ThemeTab>

  <ThemeTab name="citrine">
    <PreviewWithCode name="native-select">
      <NativeSelectDemo />
    </PreviewWithCode>
  </ThemeTab>

  <ThemeTab name="cinnamon">
    <PreviewWithCode name="native-select">
      <NativeSelectDemo />
    </PreviewWithCode>
  </ThemeTab>
</ComponentThemeTabs>

## Option groups [#option-groups]

Use `NativeSelectOptGroup` to group related options with a visible `label` heading.

```tsx
<NativeSelect>
  <NativeSelectOptGroup label="Europe">
    <NativeSelectOption value="pt">Portugal</NativeSelectOption>
    <NativeSelectOption value="de">Germany</NativeSelectOption>
  </NativeSelectOptGroup>
  <NativeSelectOptGroup label="Americas">
    <NativeSelectOption value="us">United States</NativeSelectOption>
  </NativeSelectOptGroup>
</NativeSelect>
```

## Small size [#small-size]

Use `size="sm"` for compact contexts such as toolbars, table cells, or filter rows.

```tsx
<NativeSelect size="sm">
  <NativeSelectOption value="asc">A → Z</NativeSelectOption>
  <NativeSelectOption value="desc">Z → A</NativeSelectOption>
</NativeSelect>
```

## Disabled [#disabled]

```tsx
<NativeSelect disabled>
  <NativeSelectOption value="next">Next.js</NativeSelectOption>
</NativeSelect>
```

## Invalid [#invalid]

Set `aria-invalid` to apply the destructive ring and border. Pair with `Field` and `FieldError` for an accessible error message.

```tsx
<NativeSelect aria-invalid>
  <NativeSelectOption value="" disabled>Select a value</NativeSelectOption>
</NativeSelect>
```

## Controlled [#controlled]

```tsx
const [value, setValue] = useState("next");

<NativeSelect value={value} onChange={(e) => setValue(e.target.value)}>
  <NativeSelectOption value="next">Next.js</NativeSelectOption>
  <NativeSelectOption value="remix">Remix</NativeSelectOption>
</NativeSelect>
```

## With Field [#with-field]

```tsx
import { Field, FieldDescription, FieldError, FieldLabel, useField } from "@/components/ui/field";

function FieldSelect(props: React.ComponentProps<typeof NativeSelect>) {
  const { id, error } = useField();
  return (
    <NativeSelect
      id={id}
      aria-invalid={!!error}
      aria-describedby={error ? `${id}-error` : `${id}-description`}
      {...props}
    />
  );
}

<Field error="Please select a country.">
  <FieldLabel>Country</FieldLabel>
  <FieldSelect>
    <NativeSelectOption value="" disabled>Select a country</NativeSelectOption>
    <NativeSelectOption value="pt">Portugal</NativeSelectOption>
  </FieldSelect>
  <FieldError />
</Field>
```

## NativeSelect vs Select [#nativeselect-vs-select]

|                       | NativeSelect       | Select                 |
| --------------------- | ------------------ | ---------------------- |
| Rendering             | OS-native dropdown | Custom Radix popover   |
| Mobile                | Full native UX     | Custom overlay         |
| Searchable            | No                 | Via Command (Combobox) |
| Custom option styling | Limited            | Full                   |
| Form submission       | Native             | Needs hidden input     |

<span className="block">
  Use 

  `NativeSelect`

   when native mobile UX, form submission semantics, or simplicity matters. Use 

  `Select`

   when you need custom option rendering or search.
</span>

## Props [#props]

### NativeSelect [#nativeselect]

<span className="block">
  Extends all native 

  `<select>`

   props except 

  `size`

   (replaced by GIN's own size variant).
</span>

| Prop           | Type                | Default     | Description                                     |
| -------------- | ------------------- | ----------- | ----------------------------------------------- |
| `size`         | `"default" \| "sm"` | `"default"` | Controls height, padding and text size          |
| `aria-invalid` | `boolean`           | —           | Applies destructive border and ring             |
| `disabled`     | `boolean`           | `false`     | Disables the select and reduces wrapper opacity |

### NativeSelectOption [#nativeselectoption]

<span className="block">
  Extends all native 

  `<option>`

   props. Use 

  `value`

  , 

  `disabled`

  , and text content.
</span>

### NativeSelectOptGroup [#nativeselectoptgroup]

<span className="block">
  Extends all native 

  `<optgroup>`

   props. Use 

  `label`

   for the group heading.
</span>

## Usage [#usage]

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

<NativeSelect defaultValue="">
  <NativeSelectOption value="" disabled>
    Select a framework
  </NativeSelectOption>
  <NativeSelectOption value="next">Next.js</NativeSelectOption>
  <NativeSelectOption value="remix">Remix</NativeSelectOption>
</NativeSelect>
```

## Specs [#specs]

```md
# Mint — Native Select Specs

## Component
- name: native-select
- file: registry/mint/native-select.tsx
- installs to: components/ui/native-select.tsx
- dependencies: lucide-react
- registryDependencies: none

## What is it
A styled wrapper around the native `<select>` element. It strips OS chrome
(appearance-none) and applies design-system tokens for border, focus ring,
error state, and disabled state, while a custom ChevronDown icon replaces
the OS dropdown arrow. Prefer NativeSelect over Select when native mobile
UX, form submission semantics, or minimal complexity matters most.

## Visual identity
Clean input aesthetic with a transparent background and a single border-input
border. The rounded-md radius (default) and rounded-sm (sm) match other form
controls. A muted, half-opacity chevron sits flush right and resizes with the
size variant.

## Design tokens
--input: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%)     — border colour (border-input)
--ring: oklch(0.5 0.14 155) / oklch(0.62 0.15 155)          — focus border and ring (focus-visible:border-ring, ring-ring/50)
--destructive: oklch(0.55 0.25 15) / oklch(0.704 0.191 22.216) — error border and ring (aria-invalid)
--primary: oklch(0.5 0.14 155) / oklch(0.62 0.15 155)       — text selection background (selection:bg-primary)
--primary-foreground: oklch(1 0 0) / oklch(0.1 0 0) — text selection foreground
--muted-foreground: oklch(0.52 0.06 155) / oklch(0.6 0.06 155)   — chevron icon colour

## Sub-components
- NativeSelect         → wrapper div + `<select>` + ChevronDown icon; accepts size, onChange, and all native select props
- NativeSelectOption   → `<option>` with system-color fallbacks (bg-[Canvas] text-[CanvasText])
- NativeSelectOptGroup → `<optgroup>` with system-color fallbacks; use label prop for the group heading

## External dependencies
- lucide-react: provides ChevronDownIcon for the custom dropdown arrow

## Variants
- default → h-9 px-3 pr-9 text-sm rounded-md. Standard form field height.
- sm      → h-8 px-2.5 pr-7 text-xs rounded-sm. Compact contexts: toolbars, table cells, filter rows.

## States
- focus-visible: border changes to --ring, 3px ring at ring/50 opacity
- aria-invalid:  border-destructive + ring-destructive/20 (dark: /40) — apply when field has a validation error
- disabled:      pointer-events-none cursor-not-allowed on the select; has-[select:disabled]:opacity-50 dims the entire wrapper
- dark idle:     bg-input/30 base + bg-input/50 on hover for subtle surface depth

## Conventions
- The wrapper div is sized w-fit by default; apply w-full on the wrapper or use a layout container to stretch the field
- sounds: GIN Design exclusive — onChange plays the "select" audio feedback via useFeedback
- Sub-components expose data-slot="native-select-wrapper", data-slot="native-select", data-slot="native-select-icon", data-slot="native-select-option", and data-slot="native-select-optgroup" for CSS targeting
- Always pair with Field + FieldLabel + FieldError for accessible labelling and error messaging
- Pass aria-invalid={!!error} and aria-describedby wired to the FieldError id to surface validation state accessibly
- Do not override the pr-9 / pr-7 right padding on the select without also repositioning the chevron icon
```

## Installation [#installation]

<CommandBox componentName="native-select" className="mt-2" />
