suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
GitHubGet started
DocsComponentsPatternsInteractionsChartsBlocks
Components82
  • Overview
  • Accordion
  • Alert
  • Alert Dialog
  • Alert Sheet
  • Amount Input
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Bottom Bar
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Chip
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • FAB
  • Field
  • Fullscreen Menu
  • Haptics
  • Header
  • Hover Card
  • Icon
  • Input
  • Input Accessory Bar
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Keyboard Scroll View
  • Keypad
  • Label
  • Marker
  • Menubar
  • Message
  • Message Scroller
  • Native Select
  • Navigation Menu
  • Page Indicator
  • Pagination
  • Pin Pad
  • Popover
  • Progress
  • QR Scanner
  • Questionnaire
  • Radio Group
  • Resizable
  • Screen
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Signature Pad
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Tab Bar
  • Table
  • Tabs
  • Text
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip
  • Wheel Picker
  • Widget

suiss/ui · Component

Native Select

A styled native select element for choosing one option from a list.

Native Select is an open-source component from suiss/ui for React. The @esuiss/ui CLI copies its source into your project, so you own the code.

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

export default function NativeSelectDemo() {
  return (
    <NativeSelect defaultValue="" aria-label="Fruit">
      <NativeSelectOption value="" disabled>
        Select a fruit
      </NativeSelectOption>
      <NativeSelectOption value="apple">Apple</NativeSelectOption>
      <NativeSelectOption value="banana">Banana</NativeSelectOption>
      <NativeSelectOption value="cherry">Cherry</NativeSelectOption>
      <NativeSelectOption value="grape">Grape</NativeSelectOption>
    </NativeSelect>
  )
}

NativeSelect wraps the browser's select element with the library's control styling and a trailing up-down caret. Add choices with NativeSelectOption, and group them under headings with NativeSelectOptGroup.

Because it uses the platform picker, it works well on touch devices and in plain HTML forms. Use Select when you need a custom-styled popup, rich option content or a searchable list.

Installation

CLI

$ pnpm dlx shadcn@latest add @esuiss/native-select

Works in any project with a components.json. See Installation for the one-line registry setup. Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add native-select

Manual

  1. Install the dependencies and add the cn helper:
$ pnpm add @phosphor-icons/react
  1. Copy the source into components/ui/native-select.tsx:
components/ui/native-select.tsx
"use client"

import { CaretUpDownIcon } from "@phosphor-icons/react"
import type * as React from "react"
import { cn } from "@/lib/utils"

type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
  size?: "sm" | "default"
}

function NativeSelect({
  className,
  size = "default",
  ...props
}: NativeSelectProps) {
  return (
  1. Update the import paths to match your project.

Usage

import { NativeSelect, NativeSelectOption, NativeSelectOptGroup } from "@/components/ui/native-select"
<NativeSelect defaultValue="" aria-label="Fruit">
  <NativeSelectOption value="" disabled>
    Select a fruit
  </NativeSelectOption>
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  <NativeSelectOption value="banana">Banana</NativeSelectOption>
  <NativeSelectOption value="cherry">Cherry</NativeSelectOption>
  <NativeSelectOption value="grape">Grape</NativeSelectOption>
</NativeSelect>

All standard select attributes, such as value, defaultValue, onChange, name, disabled and required, pass through to the select element. className is applied to the wrapper, not the select.

To show a placeholder, add a first option with value="" and disabled; while it is selected, the text uses text-muted-foreground.

Examples

Sizes

The sm and default sizes side by side.

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

export default function NativeSelectSizes() {
  return (
    <div className="flex items-center gap-3">
      <NativeSelect defaultValue="weekly" size="sm" aria-label="Frequency">
        <NativeSelectOption value="daily">Daily</NativeSelectOption>
        <NativeSelectOption value="weekly">Weekly</NativeSelectOption>
        <NativeSelectOption value="monthly">Monthly</NativeSelectOption>
      </NativeSelect>
      <NativeSelect defaultValue="weekly" size="default" aria-label="Frequency">
        <NativeSelectOption value="daily">Daily</NativeSelectOption>
        <NativeSelectOption value="weekly">Weekly</NativeSelectOption>
        <NativeSelectOption value="monthly">Monthly</NativeSelectOption>
      </NativeSelect>
    </div>
  )
}

With groups

Options organized under labelled NativeSelectOptGroup headings.

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

export default function NativeSelectWithGroups() {
  return (
    <NativeSelect defaultValue="berlin" aria-label="City">
      <NativeSelectOptGroup label="Europe">
        <NativeSelectOption value="berlin">Berlin</NativeSelectOption>
        <NativeSelectOption value="lisbon">Lisbon</NativeSelectOption>
        <NativeSelectOption value="zurich">Zurich</NativeSelectOption>
      </NativeSelectOptGroup>
      <NativeSelectOptGroup label="Asia">
        <NativeSelectOption value="seoul">Seoul</NativeSelectOption>
        <NativeSelectOption value="singapore">Singapore</NativeSelectOption>
        <NativeSelectOption value="tokyo">Tokyo</NativeSelectOption>
      </NativeSelectOptGroup>
    </NativeSelect>
  )
}

Disabled

A select with disabled set, dimmed to 50% opacity.

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

export default function NativeSelectDisabled() {
  return (
    <NativeSelect disabled defaultValue="" aria-label="Fruit">
      <NativeSelectOption value="" disabled>
        Select a fruit
      </NativeSelectOption>
      <NativeSelectOption value="apple">Apple</NativeSelectOption>
      <NativeSelectOption value="banana">Banana</NativeSelectOption>
      <NativeSelectOption value="cherry">Cherry</NativeSelectOption>
      <NativeSelectOption value="grape">Grape</NativeSelectOption>
    </NativeSelect>
  )
}

Invalid

A select with aria-invalid set, shown with the destructive border and ring.

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

export default function NativeSelectInvalid() {
  return (
    <NativeSelect defaultValue="" aria-invalid aria-label="Fruit">
      <NativeSelectOption value="" disabled>
        Select a fruit
      </NativeSelectOption>
      <NativeSelectOption value="apple">Apple</NativeSelectOption>
      <NativeSelectOption value="banana">Banana</NativeSelectOption>
      <NativeSelectOption value="cherry">Cherry</NativeSelectOption>
      <NativeSelectOption value="grape">Grape</NativeSelectOption>
    </NativeSelect>
  )
}

API Reference

Anatomy4 parts
<NativeSelect>
<NativeSelectOption />
<NativeSelectOptGroup>
<NativeSelectOption />
</NativeSelectOptGroup>
</NativeSelect>
01

NativeSelect

The wrapper and native select element, with a decorative caret icon.

PropTypeDefaultDescription
size"sm" | "default""default"The control height.

Also accepts the props of NativeSelectProps.

02

NativeSelectOption

A single choice, rendered as an option element.

Also accepts the props of React.ComponentProps<"option">.

03

NativeSelectOptGroup

A labelled group of options, rendered as an optgroup element.

Also accepts the props of React.ComponentProps<"optgroup">.

Accessibility

  • Renders a native select, exposed as a combobox with native keyboard and picker behavior.
  • Provide an accessible name with a Label linked through htmlFor, or with aria-label.
  • NativeSelectOptGroup exposes its label as a named group.
  • Set aria-invalid to communicate a validation error; it also applies the invalid styling.
  • The caret icon is aria-hidden.

Notes

  • Styling hooks: data-slot values native-select-wrapper, native-select, native-select-icon, native-select-option and native-select-optgroup. The wrapper and select both expose data-size.
  • The size prop accepts default (44px tall) or sm (36px tall) and replaces the native size attribute.
  • Options and groups use the Canvas and CanvasText system colors so the native popup stays legible in light and dark modes.
  • Uses the secondary, destructive and muted-foreground colors, and opacity-50 when disabled.
NextNavigation MenuPreviousMessage Scroller

On this page

  1. 01Installation
    • CLI
    • Manual
  2. 02Usage
  3. 03Examples
    • Sizes
    • With groups
    • Disabled
    • Invalid
  4. 04API Reference
    • NativeSelect
    • NativeSelectOption
    • NativeSelectOptGroup
  5. 05Accessibility
  6. 06Notes