Capítulo 28 de 108

Select

Core Idea

Exibe uma lista de opções para o usuário escolher, acionada por um botão (trigger custom-styled). Use quando precisar de estilização, animação ou interações complexas além do <select> nativo.

Key Concepts

  • items: array de { label, value } passado ao Select (e reutilizado no map de SelectItem); um item com value: null funciona como placeholder/opção vazia.
  • SelectTrigger / SelectValue: botão que abre o popup e exibe o valor selecionado (ou placeholder).
  • SelectContent: popup com as opções; contém SelectGroup, SelectLabel, SelectItem, SelectSeparator.
  • alignItemWithTrigger (em SelectContent): quando true (default), o popup posiciona o item selecionado sobre o trigger; quando false, alinha pela borda do trigger.
  • disabled: no Select desabilita tudo; em SelectItem desabilita uma opção específica.
  • aria-invalid: em SelectTrigger, combinado com data-invalid no Field, para estado de erro.
  • Base: construído sobre @base-ui/react (Base UI Select).

Code Examples

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

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
]

export function SelectDemo() {
  return (
    <Select items={items}>
      <SelectTrigger className="w-full max-w-48">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          <SelectLabel>Fruits</SelectLabel>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
  )
}
  • O que demonstra: uso básico com items compartilhado entre o array de dados e o render dos SelectItem.
<SelectContent>
  <SelectGroup>
    <SelectLabel>Fruits</SelectLabel>
    {fruits.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}
  </SelectGroup>
  <SelectSeparator />
  <SelectGroup>
    <SelectLabel>Vegetables</SelectLabel>
    {vegetables.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}
  </SelectGroup>
</SelectContent>
  • O que demonstra: agrupamento de itens com SelectGroup + SelectLabel, separados por SelectSeparator.

Reference Tables

Composição:

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

Anti-patterns

  • Duplicar a lista de opções: definir items só para o Select e um segundo array só pro .map() diverge do padrão da doc, que reutiliza o mesmo items.
  • Usar Select quando NativeSelect bastaria: em formulários simples/mobile-first sem necessidade de estilo customizado, prefira NativeSelect (melhor performance e comportamento nativo).

Key Takeaways

  1. items é passado tanto ao Select (para lógica interna, ex. exibir label do valor atual) quanto reaproveitado no .map() dos SelectItem — mantenha uma única fonte de dados.
  2. alignItemWithTrigger={false} em SelectContent alinha o popup pela borda do trigger em vez de sobrepor o item selecionado; útil quando o alinhamento por overlay quebra o layout.
  3. Estado inválido é duplo: aria-invalid no SelectTrigger + data-invalid no Field; use FieldError para a mensagem.
  4. SelectItem com disabled desabilita apenas aquela opção, mantendo as demais clicáveis.
  5. RTL funciona propagando dir para SelectTrigger e SelectContent.

Connects To

  • Native Select: alternativa nativa mais leve, sem estilização/animação customizada.
  • Field / FieldError: fornecem label, descrição e mensagem de erro em volta do Select.
  • Switch: usado em exemplos de configuração ao lado do Select (ex: toggle alignItemWithTrigger).