HextaUI

Select

Escolha uma ou mais opções de uma lista que abre no valor atual, com typeahead, grupos e suporte a formulários.

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

Adiciona o componente, os tokens de tema do HextaUI e quaisquer componentes do HextaUI dos quais ele depende.

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>

A lista abre bem sobre o gatilho com a opção atual alinhada sobre o valor, para o olhar nunca perder o lugar. Passe items para que SelectValue mostre rótulos em vez de valores brutos. Para listas longas que precisam de busca, use Combobox.

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

Tamanhos

size em SelectTrigger combina com as alturas de Input e 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>
  )
}

Grupos e listas longas

Agrupe opções com SelectGroup e SelectLabel. Listas longas cabem na tela e mostram setas de rolagem que rolam quando você passa o mouse.

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>
  )
}

Com ícones

Coloque ícones nos itens e passe uma função para SelectValue para mostrar o mesmo ícone no gatilho.

"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>
  )
}

Múltiplo

Com multiple, a lista permanece aberta enquanto você escolhe, e o valor pode resumir seleções longas.

"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>
  )
}

Em um formulário

Dentro de Field, o gatilho recebe seu rótulo, descrição e validação de obrigatório.

"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>
  )
}

Desativado e inválido

Desabilite o select inteiro ou opções individuais, e marque-o como inválido com aria-invalid.

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>
  )
}

Abaixo do gatilho

alignItemWithTrigger={false} abre a lista sob o gatilho como um menu. A entrada por toque faz isso automaticamente.

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>
  )
}

Da direita para a esquerda

O gatilho, a lista e o check seguem a direção de leitura.

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>
  )
}
TeclaAção
SpaceEnter↓↑Abre a lista a partir do gatilho.
↓↑Move entre as opções.
HomeEndMove para a primeira ou a última opção.
A–ZSalta para a próxima opção que começa com o texto digitado.
EnterSpaceEscolhe a opção destacada.
EscFecha a lista e devolve o foco ao gatilho.
  • Rotule o gatilho com FieldLabel ou aria-label.
  • Em toque, a lista abre abaixo do gatilho em vez de sobre ele, para seu dedo não cair sobre uma opção.
PropTipoPadrão
value
Value | Value[] | null–
defaultValue
Value | Value[] | null–
onValueChange
(value, details) => void–
itemsPermite que SelectValue mostre rótulos.
Record<string, ReactNode> | { value, label }[]–
multiple
booleanfalse
name
string–
required
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
open
boolean–
onOpenChange
(open, details) => void–
PropTipoPadrão
size
"sm" | "default" | "lg""default"
render
ReactElement | (props, state) => ReactElement<button>
AtributoDescrição
data-slot="select-trigger"O gatilho, com data-size.
data-popup-openPresente enquanto a lista está aberta.
data-placeholderPresente enquanto nada está escolhido.
PropTipoPadrão
placeholder
ReactNode–
childrenFormata o valor exibido.
ReactNode | (value) => ReactNode–
PropTipoPadrão
alignItemWithTriggerAbre sobre o gatilho com a opção atual alinhada.
booleantrue
sideQuando não está alinhado com o gatilho.
"top" | "bottom" | …"bottom"
align
"start" | "center" | "end""start"
sideOffset
number6
AtributoDescrição
data-slot="select-content"O popup.
data-side="none"Presente enquanto alinhado sobre o gatilho.
PropTipoPadrão
value
Value–
disabled
booleanfalse
labelTexto para o typeahead.
string–
AtributoDescrição
data-selectedA opção escolhida.
data-highlightedA opção com foco.
data-disabledA opção está desabilitada.

Usado em blocos

Blocos que se baseiam em Select.