HextaUI

Select

Wähle eine oder mehrere Optionen aus einer Liste, die beim aktuellen Wert öffnet, mit Typeahead, Gruppen und Formularunterstützung.

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const fonts = [
  { value: "inter", label: "Inter" },
  { value: "geist", label: "Geist" },
  { value: "ibm-plex", label: "IBM Plex Sans" },
  { value: "source-serif", label: "Source Serif" },
  { value: "jetbrains", label: "JetBrains Mono" },
]

export function SelectDemo() {
  return (
    <Select items={fonts} defaultValue="geist">
      <SelectTrigger aria-label="Font" className="w-48">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        {fonts.map((font) => (
          <SelectItem key={font.value} value={font.value}>
            {font.label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/select.json

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

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
<Select items={fonts} defaultValue="geist">
  <SelectTrigger aria-label="Font">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    {fonts.map((font) => (
      <SelectItem key={font.value} value={font.value}>
        {font.label}
      </SelectItem>
    ))}
  </SelectContent>
</Select>

Die Liste öffnet sich direkt über dem Trigger, mit der aktuellen Option über dem Wert ausgerichtet, sodass dein Auge nie den Platz verliert. Übergib items, damit SelectValue Labels statt Rohwerten zeigt. Für lange Listen, die durchsucht werden müssen, nutze Combobox.

Select
├── SelectTrigger
│   └── SelectValue
└── SelectContent
    ├── SelectGroup
    │   ├── SelectLabel
    │   └── SelectItem
    └── SelectSeparator

Größen

size an SelectTrigger entspricht den Höhen von Input und Button.

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

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

export function SelectSizes() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {sizes.map((size) => (
        <Select key={size} defaultValue="week">
          <SelectTrigger size={size} aria-label={`Range (${size})`}>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="day">Today</SelectItem>
            <SelectItem value="week">This week</SelectItem>
            <SelectItem value="month">This month</SelectItem>
          </SelectContent>
        </Select>
      ))}
    </div>
  )
}

Gruppen und lange Listen

Gruppiere Optionen mit SelectGroup und SelectLabel. Lange Listen passen auf den Bildschirm und zeigen Scrollpfeile, die beim Hovern scrollen.

import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const zones = [
  {
    label: "Americas",
    items: [
      "Los Angeles",
      "Denver",
      "Chicago",
      "New York",
      "Toronto",
      "Mexico City",
      "Bogotá",
      "São Paulo",
      "Buenos Aires",
    ],
  },
  {
    label: "Europe",
    items: ["London", "Lisbon", "Paris", "Berlin", "Stockholm", "Athens"],
  },
  {
    label: "Asia",
    items: ["Dubai", "Mumbai", "Singapore", "Shanghai", "Tokyo", "Seoul"],
  },
]

export function SelectGroups() {
  return (
    <Select defaultValue="Berlin">
      <SelectTrigger aria-label="Time zone" className="w-56">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        {zones.map((zone, index) => (
          <SelectGroup key={zone.label}>
            {index > 0 ? <SelectSeparator /> : null}
            <SelectLabel>{zone.label}</SelectLabel>
            {zone.items.map((city) => (
              <SelectItem key={city} value={city}>
                {city}
              </SelectItem>
            ))}
          </SelectGroup>
        ))}
      </SelectContent>
    </Select>
  )
}

Mit Icons

Setze Icons in Items und übergib SelectValue eine Funktion, um dasselbe Icon im Trigger zu zeigen.

"use client"

import {
  IconCircleCheck,
  IconCircleDashed,
  IconCircleHalf2,
  IconCircleX,
} from "@tabler/icons-react"

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const statuses = [
  { value: "backlog", label: "Backlog", icon: IconCircleDashed },
  { value: "progress", label: "In progress", icon: IconCircleHalf2 },
  { value: "done", label: "Done", icon: IconCircleCheck },
  { value: "canceled", label: "Canceled", icon: IconCircleX },
]

export function SelectIcons() {
  return (
    <Select defaultValue="progress">
      <SelectTrigger aria-label="Status" className="w-44">
        <SelectValue>
          {(value: string) => {
            const status = statuses.find((item) => item.value === value)
            if (!status) {
              return null
            }
            const Icon = status.icon
            return (
              <>
                <Icon />
                {status.label}
              </>
            )
          }}
        </SelectValue>
      </SelectTrigger>
      <SelectContent>
        {statuses.map((status) => (
          <SelectItem key={status.value} value={status.value}>
            <status.icon />
            {status.label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  )
}

Mehrfach

Mit multiple bleibt die Liste beim Wählen offen, und der Wert kann lange Auswahlen zusammenfassen.

"use client"

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const labels = ["Bug", "Feature", "Design", "Docs", "Performance"]

export function SelectMultiple() {
  return (
    <Select multiple defaultValue={["Bug", "Design"]}>
      <SelectTrigger aria-label="Labels" className="w-56">
        <SelectValue placeholder="Add labels">
          {(value: string[]) =>
            value.length > 2 ? `${value.length} labels` : value.join(", ")
          }
        </SelectValue>
      </SelectTrigger>
      <SelectContent alignItemWithTrigger={false}>
        {labels.map((label) => (
          <SelectItem key={label} value={label}>
            {label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  )
}

In einem Formular

In Field erhält der Trigger sein Label, seine Beschreibung und Pflichtvalidierung.

"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 {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const roles = [
  { value: "viewer", label: "Viewer" },
  { value: "editor", label: "Editor" },
  { value: "admin", label: "Admin" },
]

export function SelectField() {
  return (
    <Form
      className="flex w-full max-w-xs flex-col items-start gap-4"
      onSubmit={(event) => event.preventDefault()}
    >
      <Field name="role">
        <FieldLabel>Role</FieldLabel>
        <Select items={roles} required>
          <SelectTrigger className="w-full">
            <SelectValue placeholder="Choose a role" />
          </SelectTrigger>
          <SelectContent>
            {roles.map((role) => (
              <SelectItem key={role.value} value={role.value}>
                {role.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
        <FieldDescription>Admins can invite people.</FieldDescription>
        <FieldError match="valueMissing">Choose a role to continue.</FieldError>
      </Field>
      <Button type="submit" size="sm">
        Invite
      </Button>
    </Form>
  )
}

Deaktiviert und ungültig

Deaktiviere das ganze Select oder einzelne Optionen und markiere es mit aria-invalid als ungültig.

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function SelectStates() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Select defaultValue="pro" disabled>
        <SelectTrigger aria-label="Plan" className="w-36">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="pro">Pro</SelectItem>
        </SelectContent>
      </Select>
      <Select defaultValue="weekly">
        <SelectTrigger aria-label="Digest" className="w-36">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="daily">Daily</SelectItem>
          <SelectItem value="weekly">Weekly</SelectItem>
          <SelectItem value="monthly" disabled>
            Monthly (soon)
          </SelectItem>
        </SelectContent>
      </Select>
      <Select>
        <SelectTrigger aria-label="Region" aria-invalid className="w-36">
          <SelectValue placeholder="Region" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="us">United States</SelectItem>
          <SelectItem value="eu">Europe</SelectItem>
        </SelectContent>
      </Select>
    </div>
  )
}

Unter dem Trigger

alignItemWithTrigger={false} öffnet die Liste wie ein Menü unter dem Trigger. Touch-Eingabe tut das automatisch.

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

export function SelectDropdown() {
  return (
    <Select defaultValue="newest">
      <SelectTrigger aria-label="Sort by" className="w-44">
        <SelectValue />
      </SelectTrigger>
      <SelectContent alignItemWithTrigger={false}>
        <SelectItem value="newest">Newest first</SelectItem>
        <SelectItem value="oldest">Oldest first</SelectItem>
        <SelectItem value="popular">Most popular</SelectItem>
      </SelectContent>
    </Select>
  )
}

Rechts nach links

Trigger, Liste und Häkchen folgen der Leserichtung.

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const cities = [
  { value: "cairo", label: "القاهرة" },
  { value: "riyadh", label: "الرياض" },
  { value: "dubai", label: "دبي" },
]

export function SelectRtl() {
  return (
    <div dir="rtl">
      <Select items={cities} defaultValue="riyadh">
        <SelectTrigger aria-label="المدينة" className="w-44">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {cities.map((city) => (
            <SelectItem key={city.value} value={city.value}>
              {city.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
TasteAktion
SpaceEnter↓↑Öffnet die Liste vom Trigger aus.
↓↑Wechselt zwischen Optionen.
HomeEndSpringt zur ersten oder letzten Option.
A–ZSpringt zur nächsten Option, die mit dem getippten Text beginnt.
EnterSpaceWählt die hervorgehobene Option.
EscSchließt die Liste und gibt den Fokus an den Trigger zurück.
  • Beschrifte den Trigger mit FieldLabel oder aria-label.
  • Bei Touch öffnet sich die Liste unter dem Trigger statt darüber, damit dein Finger nicht auf einer Option landet.
PropTypStandard
value
Value | Value[] | null–
defaultValue
Value | Value[] | null–
onValueChange
(value, details) => void–
itemsLässt SelectValue Labels anzeigen.
Record<string, ReactNode> | { value, label }[]–
multiple
booleanfalse
name
string–
required
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
open
boolean–
onOpenChange
(open, details) => void–
PropTypStandard
size
"sm" | "default" | "lg""default"
render
ReactElement | (props, state) => ReactElement<button>
AttributBeschreibung
data-slot="select-trigger"Der Trigger, mit data-size.
data-popup-openVorhanden, solange die Liste offen ist.
data-placeholderVorhanden, solange nichts gewählt ist.
PropTypStandard
placeholder
ReactNode–
childrenFormatiere den angezeigten Wert.
ReactNode | (value) => ReactNode–
PropTypStandard
alignItemWithTriggerÖffnet sich über dem Trigger, mit der aktuellen Option darauf ausgerichtet.
booleantrue
sideWenn nicht am Trigger ausgerichtet.
"top" | "bottom" | …"bottom"
align
"start" | "center" | "end""start"
sideOffset
number6
AttributBeschreibung
data-slot="select-content"Das Popup.
data-side="none"Vorhanden, solange sie über dem Trigger ausgerichtet ist.
PropTypStandard
value
Value–
disabled
booleanfalse
labelText für Typeahead.
string–
AttributBeschreibung
data-selectedDie gewählte Option.
data-highlightedDie fokussierte Option.
data-disabledDie Option ist deaktiviert.

In Blocks verwendet

Blocks, die auf Select aufbauen.