HextaUI

Native select

Das native Select des Browsers, passend zu Input gestylt, mit dezentem Platzhalter, Field-Unterstützung und dem Picker des Betriebssystems auf jedem Gerät.

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select"

export function NativeSelectDemo() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      <div className="flex flex-col gap-2">
        <Label htmlFor="native-select-demo-name">Project name</Label>
        <Input id="native-select-demo-name" defaultValue="HextaUI" />
      </div>
      <div className="flex flex-col gap-2">
        <Label htmlFor="native-select-demo-framework">Framework</Label>
        <NativeSelect id="native-select-demo-framework" className="w-full">
          <NativeSelectOption value="">Select a framework</NativeSelectOption>
          <NativeSelectOption value="next">Next.js</NativeSelectOption>
          <NativeSelectOption value="remix">Remix</NativeSelectOption>
          <NativeSelectOption value="astro">Astro</NativeSelectOption>
          <NativeSelectOption value="vite">Vite</NativeSelectOption>
        </NativeSelect>
      </div>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/native-select.json

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

import {
  NativeSelect,
  NativeSelectOptGroup,
  NativeSelectOption,
} from "@/components/ui/native-select"
<NativeSelect>
  <NativeSelectOption value="">Select a fruit</NativeSelectOption>
  <NativeSelectOption value="apple">Apple</NativeSelectOption>
  <NativeSelectOption value="banana">Banana</NativeSelectOption>
</NativeSelect>

Eine Option mit leerem value dient als Platzhalter: Sie erscheint in gedämpftem Text, bis etwas gewählt wird. Setze className am Select, um seinen Wrapper zu dimensionieren, zum Beispiel w-full.

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

Gruppen

<NativeSelectOptGroup /> ordnet Optionen im Picker des Betriebssystems unter Überschriften.

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

export function NativeSelectGroups() {
  return (
    <NativeSelect aria-label="Department">
      <NativeSelectOption value="">Select department</NativeSelectOption>
      <NativeSelectOptGroup label="Engineering">
        <NativeSelectOption value="frontend">Frontend</NativeSelectOption>
        <NativeSelectOption value="backend">Backend</NativeSelectOption>
        <NativeSelectOption value="devops">DevOps</NativeSelectOption>
      </NativeSelectOptGroup>
      <NativeSelectOptGroup label="Design">
        <NativeSelectOption value="product">Product design</NativeSelectOption>
        <NativeSelectOption value="brand">Brand</NativeSelectOption>
      </NativeSelectOptGroup>
      <NativeSelectOptGroup label="Operations">
        <NativeSelectOption value="support">
          Customer support
        </NativeSelectOption>
        <NativeSelectOption value="people">People</NativeSelectOption>
      </NativeSelectOptGroup>
    </NativeSelect>
  )
}

Größen

Größen, Rahmen, Fokusringe und Invalid-Styles entsprechen genau Input, sodass beide in einer Zeile fluchten.

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

const sizes = ["sm", "default", "lg"] as const

export function NativeSelectSizes() {
  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      {sizes.map((size) => (
        <div key={size} className="flex gap-2">
          <Input
            size={size}
            placeholder={`Input ${size}`}
            aria-label={`Input ${size}`}
          />
          <NativeSelect size={size} aria-label={`Select ${size}`}>
            <NativeSelectOption value="">{size}</NativeSelectOption>
            <NativeSelectOption value="a">Option A</NativeSelectOption>
          </NativeSelect>
        </div>
      ))}
    </div>
  )
}

Field

In einem <Field /> verbinden sich Label und Beschreibung von selbst, und Fehler erscheinen, sobald sich der Wert ändert. Sende ohne Tarif ab, und das Select wird rot, wackelt einmal, und der Browser springt dorthin.

"use client"

import { Form } from "@base-ui/react/form"

import { Button } from "@/components/ui/button"
import {
  Field,
  FieldDescription,
  FieldError,
  FieldLabel,
} from "@/components/ui/field"
import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select"

export function NativeSelectField() {
  return (
    <Form
      className="flex w-full max-w-sm flex-col gap-4"
      onSubmit={(event) => event.preventDefault()}
    >
      <Field validationMode="onChange">
        <FieldLabel>Plan</FieldLabel>
        <NativeSelect name="plan" required className="w-full">
          <NativeSelectOption value="">Choose a plan</NativeSelectOption>
          <NativeSelectOption value="hobby">Hobby</NativeSelectOption>
          <NativeSelectOption value="pro">Pro</NativeSelectOption>
          <NativeSelectOption value="team">Team</NativeSelectOption>
        </NativeSelect>
        <FieldDescription>You can change this later.</FieldDescription>
        <FieldError match="valueMissing">Choose a plan to continue.</FieldError>
      </Field>
      <Button type="submit" className="self-start">
        Continue
      </Button>
    </Form>
  )
}

Ungültig

Setze aria-invalid und verknüpfe die Meldung mit aria-describedby.

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

export function NativeSelectInvalid() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2">
      <NativeSelect
        aria-label="Country"
        aria-invalid
        aria-describedby="native-select-invalid-message"
        className="w-full"
      >
        <NativeSelectOption value="">Select a country</NativeSelectOption>
        <NativeSelectOption value="in">India</NativeSelectOption>
        <NativeSelectOption value="de">Germany</NativeSelectOption>
      </NativeSelect>
      <p
        id="native-select-invalid-message"
        className="text-sm text-destructive"
      >
        We don&apos;t ship to the selected region yet.
      </p>
    </div>
  )
}

Deaktiviert

Deaktiviere das ganze Select oder einzelne nicht verfügbare Optionen.

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

export function NativeSelectDisabled() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <NativeSelect aria-label="Region" disabled defaultValue="eu">
        <NativeSelectOption value="us">United States</NativeSelectOption>
        <NativeSelectOption value="eu">Europe</NativeSelectOption>
      </NativeSelect>
      <NativeSelect aria-label="Plan" defaultValue="pro">
        <NativeSelectOption value="hobby">Hobby</NativeSelectOption>
        <NativeSelectOption value="pro">Pro</NativeSelectOption>
        <NativeSelectOption value="enterprise" disabled>
          Enterprise (contact sales)
        </NativeSelectOption>
      </NativeSelect>
    </div>
  )
}

Kontrolliert

Übergib value und onChange wie bei einem normalen Select.

"use client"

import * as React from "react"

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

const zones = [
  { value: "America/New_York", label: "New York" },
  { value: "Europe/London", label: "London" },
  { value: "Asia/Kolkata", label: "Kolkata" },
  { value: "Asia/Tokyo", label: "Tokyo" },
]

export function NativeSelectControlled() {
  const [zone, setZone] = React.useState("Asia/Kolkata")
  const time = new Intl.DateTimeFormat("en-US", {
    hour: "numeric",
    minute: "2-digit",
    timeZone: zone,
  }).format(new Date(Date.UTC(2026, 9, 4, 9, 0)))

  return (
    <div className="flex flex-col items-start gap-2">
      <NativeSelect
        aria-label="Time zone"
        value={zone}
        onChange={(event) => setZone(event.target.value)}
      >
        {zones.map((option) => (
          <NativeSelectOption key={option.value} value={option.value}>
            {option.label}
          </NativeSelectOption>
        ))}
      </NativeSelect>
      <p className="text-sm text-muted-foreground">
        09:00 UTC is {time} there.
      </p>
    </div>
  )
}

Langer Inhalt

Lange ausgewählte Labels enden mit einer Ellipse, statt unter dem Chevron zu verlaufen.

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

export function NativeSelectLongContent() {
  return (
    <div className="w-56 max-w-full">
      <NativeSelect
        aria-label="Billing account"
        defaultValue="long"
        className="w-full"
      >
        <NativeSelectOption value="short">Personal</NativeSelectOption>
        <NativeSelectOption value="long">
          The International Subsidiary Billing Account for Europe and Asia
        </NativeSelectOption>
      </NativeSelect>
    </div>
  )
}

Rechts nach links

Chevron und Padding wechseln auf die andere Seite.

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

export function NativeSelectRtl() {
  return (
    <div dir="rtl">
      <NativeSelect aria-label="اللغة">
        <NativeSelectOption value="">اختر لغة</NativeSelectOption>
        <NativeSelectOption value="ar">العربية</NativeSelectOption>
        <NativeSelectOption value="en">الإنجليزية</NativeSelectOption>
        <NativeSelectOption value="fr">الفرنسية</NativeSelectOption>
      </NativeSelect>
    </div>
  )
}
  • Nutze Native select für einfache Listen, besonders auf Smartphones, wo der Picker des Betriebssystems der schnellste und vertrauteste Weg zur Auswahl ist.
  • Nutze Combobox, wenn Leute suchen müssen oder Optionen Icons, Beschreibungen oder eigene Layouts brauchen.
TasteAktion
TabFokussiert das Select.
SpaceEnterÖffnet den Picker des Betriebssystems.
↑↓Ändert die Auswahl oder bewegt sich im geöffneten Picker.
A–ZSpringt zur Option, die mit dem getippten Text beginnt.

<NativeSelect /> akzeptiert alle select-Attribute. Die Option-Teile akzeptieren die Attribute ihres Elements.

PropTypStandard
size
"sm" | "default" | "lg""default"
htmlSizeDas native size-Attribut, umbenannt, weil size die Variante ist.
number–
shakeWackelt einmal, wenn ein Formular-Submit es als ungültig findet.
booleantrue
classNameWird auf den Wrapper angewendet und dimensioniert so das ganze Steuerelement.
string–
value
string–
defaultValue
string–
disabled
booleanfalse
required
booleanfalse
AttributBeschreibung
data-slot="native-select-wrapper"Der Wrapper, mit data-size.
data-slot="native-select"Das select-Element.
data-slot="native-select-icon"Das Chevron. Wird bei Hover und Fokus dunkler.
data-invalidVorhanden, wenn ein umgebendes Field ungültig ist.
AttributBeschreibung
data-slot="native-select-option"Eine Option.
data-slot="native-select-optgroup"Eine beschriftete Gruppe von Optionen.

In Blocks verwendet

Blocks, die auf Native select aufbauen.