# Select

Escolha única em lista.

Compõe com `SelectTrigger`, `SelectValue`, `SelectContent` e `SelectItem`.
Lista com famílias de verdade ganha `SelectGroup` com `SelectGroupLabel`, e
`SelectSeparator` entre uma família e outra.

**Passe `items` com `{ label, value }` na raiz.** Sem isso o gatilho mostra o
valor cru em vez do rótulo, e essa é a armadilha mais fácil de cair aqui.

Renderiza em portal, então exige o `RivoProvider`.

## Quando não usar

Quando a lista é grande demais para caber na cabeça de quem escolhe, ou quando
ela vem do servidor, use `Combobox`: ele traz a busca junto. Rolar cento e
vinte cidades numa lista sem campo de digitar é o mesmo trabalho de procurar
numa gaveta.

Para duas ou três opções que cabem lado a lado, o `RadioGroup` mostra todas de
uma vez e economiza o clique de abrir. E para um liga-desliga, o `Switch`.

## No React Native

Traduz, e a forma de escrever é outra. No web o `Select` pede `items` na raiz **e** as quatro partes (`SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`); no nativo ele é uma tag só (`<Select items={…} value={…} onValueChange={…} label="Período" />`), e a lista abre numa folha de baixo, que é o idioma da plataforma para escolher. O `label` é obrigatório: é por ele que o leitor de tela anuncia o gatilho, papel que no web era do `SelectTrigger`.

## Importação

```tsx
import { Select } from '@rivocode/ui'
```

## Exemplos

### Fechado

```tsx
import {
  Field,
  FieldLabel,
  Select,
  SelectContent,
  SelectGroup,
  SelectGroupLabel,
  SelectItem,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from '@rivocode/ui'

const PERIODOS = [
  { label: 'Ultimos 30 dias', value: '30' },
  { label: 'Ultimos 90 dias', value: '90' },
  { label: 'Este ano', value: 'ano' },
]

export function ClosedState() {
  return (
    <Select items={PERIODOS} defaultValue="30">
      <SelectTrigger aria-label="Período">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        {PERIODOS.map(o => (
          <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
        ))}
      </SelectContent>
    </Select>
  )
}
```

### Aberto

```tsx
import {
  Field,
  FieldLabel,
  Select,
  SelectContent,
  SelectGroup,
  SelectGroupLabel,
  SelectItem,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from '@rivocode/ui'

const PERIODOS = [
  { label: 'Ultimos 30 dias', value: '30' },
  { label: 'Ultimos 90 dias', value: '90' },
  { label: 'Este ano', value: 'ano' },
]

export function Open() {
  return (
    <div className="min-h-56">
      <Select items={PERIODOS} defaultValue="90" defaultOpen>
        <SelectTrigger aria-label="Período">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {PERIODOS.map(o => (
            <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  )
}
```

### Dentro de campo

```tsx
import {
  Field,
  FieldLabel,
  Select,
  SelectContent,
  SelectGroup,
  SelectGroupLabel,
  SelectItem,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from '@rivocode/ui'

const PERIODOS = [
  { label: 'Ultimos 30 dias', value: '30' },
  { label: 'Ultimos 90 dias', value: '90' },
  { label: 'Este ano', value: 'ano' },
]

const NATUREZAS = [
  { label: 'Venda de mercadoria', value: '5102', flow: 'Saída' },
  { label: 'Remessa para conserto', value: '5915', flow: 'Saída' },
  { label: 'Devolução de venda', value: '1202', flow: 'Entrada' },
  { label: 'Compra para revenda', value: '1102', flow: 'Entrada' },
]

export function InsideAField() {
  return (
    <Field name="periodo" className="max-w-xs">
      <FieldLabel>Período do relatório</FieldLabel>
      <Select items={PERIODOS} defaultValue="ano">
        <SelectTrigger aria-label="Período do relatório">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {PERIODOS.map(o => (
            <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
          ))}
        </SelectContent>
      </Select>
    </Field>
  )
}

const NATUREZAS = [
  { label: 'Venda de mercadoria', value: '5102', flow: 'Saída' },
  { label: 'Remessa para conserto', value: '5915', flow: 'Saída' },
  { label: 'Devolução de venda', value: '1202', flow: 'Entrada' },
  { label: 'Compra para revenda', value: '1102', flow: 'Entrada' },
]
```

### Agrupado por família

```tsx
import {
  Field,
  FieldLabel,
  Select,
  SelectContent,
  SelectGroup,
  SelectGroupLabel,
  SelectItem,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from '@rivocode/ui'

const NATUREZAS = [
  { label: 'Venda de mercadoria', value: '5102', flow: 'Saída' },
  { label: 'Remessa para conserto', value: '5915', flow: 'Saída' },
  { label: 'Devolução de venda', value: '1202', flow: 'Entrada' },
  { label: 'Compra para revenda', value: '1102', flow: 'Entrada' },
]

export function Grouped() {
  return (
    <div className="min-h-72">
      {/* O `items` continua sendo a lista INTEIRA e plana: e por ele que o
          gatilho traduz o valor guardado no rotulo que a pessoa leu. O grupo
          arruma a lista aberta, e nao o que o gatilho mostra. */}
      <Select items={NATUREZAS} defaultValue="5102" defaultOpen>
        <SelectTrigger aria-label="Natureza da operação" className="min-w-64">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectGroupLabel>Saída</SelectGroupLabel>
            {NATUREZAS.filter((n) => n.flow === 'Saída').map((n) => (
              <SelectItem key={n.value} value={n.value}>
                {n.label}
              </SelectItem>
            ))}
          </SelectGroup>

          <SelectSeparator />

          <SelectGroup>
            <SelectGroupLabel>Entrada</SelectGroupLabel>
            {NATUREZAS.filter((n) => n.flow === 'Entrada').map((n) => (
              <SelectItem key={n.value} value={n.value}>
                {n.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  )
}
```

## Props

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `actionsRef` | `RefObject<SelectRootActions \| null>` |  | 0.4.0 | A ref to imperative actions. |
| `autoComplete` | `string` |  | 0.4.0 | Provides a hint to the browser for autofill. |
| `defaultOpen` | `boolean` |  | 0.4.0 | Whether the select popup is initially open. |
| `defaultValue` | `SelectValueType<Value, Multiple> \| null` |  | 0.4.0 | The uncontrolled value of the select when it's initially rendered. |
| `disabled` | `boolean` |  | 0.4.0 | Whether the component should ignore user interaction. |
| `form` | `string` |  | 0.4.0 | Identifies the form that owns the hidden input. |
| `highlightItemOnHover` | `boolean` |  | 0.4.0 | Whether moving the pointer over items should highlight them. |
| `inputRef` | `Ref<HTMLInputElement>` |  | 0.4.0 | A ref to access the hidden input element. |
| `isItemEqualToValue` | `((itemValue: Value, value: Value) => boolean)` |  | 0.4.0 | Custom comparison logic used to determine if a select item value matches the current selected value. |
| `items` | `readonly Group<any>[] \| readonly { label: ReactNode; value: any; }[] \| Record<string, ReactNode>` |  | 0.4.0 | Data structure of the items rendered in the select popup. |
| `itemToStringLabel` | `((itemValue: Value) => string)` |  | 0.4.0 | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger. |
| `itemToStringValue` | `((itemValue: Value) => string)` |  | 0.4.0 | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission. |
| `modal` | `boolean` |  | 0.4.0 | Determines if the select enters a modal state when open. |
| `multiple` | `Multiple` |  | 0.4.0 | Whether multiple items can be selected. |
| `name` | `string` |  | 0.4.0 | Identifies the field when a form is submitted. |
| `onOpenChange` | `((open: boolean, eventDetails: SelectRootChangeEventDetails) => void)` |  | 0.4.0 | Event handler called when the select popup is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` |  | 0.4.0 | Event handler called after any animations complete when the select popup is opened or closed. |
| `onValueChange` | `((value: SelectValueType<Value, Multiple> \| (Multiple extends true ? never : null), eventDetails: SelectRootChangeEventDetails) => void)` |  | 0.4.0 | Event handler called when the value of the select changes. |
| `open` | `boolean` |  | 0.4.0 | Whether the select popup is currently open. |
| `readOnly` | `boolean` |  | 0.4.0 | Whether the user should be unable to choose a different option from the select popup. |
| `required` | `boolean` |  | 0.4.0 | Whether the user must choose a value before submitting a form. |
| `value` | `SelectValueType<Value, Multiple> \| null` |  | 0.4.0 | The value of the select. |

## Partes

O componente se monta com as peças abaixo. Todas vêm de `@rivocode/ui`.

### SelectContent

A lista flutuante do select.

Nasce com a largura do gatilho e rola sozinha quando não cabe na tela. O portal
usa o contêiner do `RivoProvider`, então o tema vale dentro dela.

`side`, `align` e `sideOffset` posicionam o painel, como nas outras peças que
flutuam. **Pedir qualquer um dos três troca o modo de posicionamento**: por
padrão a lista se sobrepõe ao gatilho para alinhar o item escolhido com o texto
dele, e nesse modo não há lado nem folga a respeitar. Quem não pede nada
continua com o alinhamento pelo item.

```tsx
<SelectContent side="top" align="start">
  <SelectItem value="abertas">Abertas</SelectItem>
</SelectContent>
```

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `align` | `Align` |  | - | Alinhamento no eixo do lado escolhido. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => void \| boolean \| HTMLElement \| null)` |  | 0.4.0 | Determines the element to focus when the select popup is closed. |
| `render` | `ComponentRenderFn<HTMLProps, SelectPopupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` |  | 0.4.0 | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `side` | `Side` |  | - | Lado preferido do gatilho. |
| `sideOffset` | `number \| OffsetFunction` |  | - | Distancia entre o gatilho e o painel, em pixels. |

### SelectGroup

Uma família dentro da lista, com o `SelectGroupLabel` de cabeçalho.

Natureza de operação separada em entrada e saída, UF por região, plano de contas
por grupo: a lista longa que tem famílias de verdade lê-se por partes, e não de
cima a baixo.

O desenho é o do `ComboboxGroup`, e não o do `MenuGroup` com `label`: as duas são
peças de formulário e listam opções, e quem troca uma pela outra ao descobrir que
a lista cresceu não deveria ter que reescrever a árvore.

```tsx
<SelectContent>
  <SelectGroup>
    <SelectGroupLabel>Saída</SelectGroupLabel>
    <SelectItem value="5102">Venda de mercadoria</SelectItem>
    <SelectItem value="5915">Remessa para conserto</SelectItem>
  </SelectGroup>

  <SelectSeparator />

  <SelectGroup>
    <SelectGroupLabel>Entrada</SelectGroupLabel>
    <SelectItem value="1202">Devolução de venda</SelectItem>
  </SelectGroup>
</SelectContent>
```

O `items` da raiz continua sendo a lista **inteira e plana**: é por ele que o
gatilho traduz o valor guardado no rótulo que a pessoa leu. O grupo arruma a
lista aberta, e não o que o gatilho mostra.

O `SelectGroupLabel` vive dentro do grupo porque é o grupo que aponta o
`aria-labelledby` para ele. Título escrito ao lado não nomeia nada, e nenhum
tipo reclama.

## Quando não usar

Quando agrupar é a tentativa de domar uma lista que ficou grande demais, o
remédio é outro: `Combobox`, que traz a busca. Rolar cento e vinte cidades
arrumadas por região continua sendo rolar cento e vinte cidades, e o cabeçalho só
acrescenta altura ao caminho.

Grupo de dois itens não paga o cabeçalho que cobra. Sem famílias de verdade, a
lista plana diz a mesma coisa em menos linhas.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, SelectGroupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` |  | - | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

### SelectItem

Uma opção da lista.

O `value` é o que volta no `onValueChange`; o conteúdo é o que a pessoa lê. A
marca de escolhido tem coluna própria, então rótulo curto e longo alinham pela
mesma vertical.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `disabled` | `boolean` |  | 0.4.0 | Whether the component should ignore user interaction. |
| `label` | `string` |  | 0.4.0 | Specifies the text label to use when the item is matched during keyboard text navigation. |
| `nativeButton` | `boolean` |  | 0.4.0 | Whether the component renders a native `<button>` element when replacing it via the `render` prop. |
| `render` | `ComponentRenderFn<HTMLProps, SelectItemState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` |  | 0.4.0 | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `value` | `any` |  | 0.4.0 | A unique value that identifies this select item. |

### SelectSeparator

A linha entre dois grupos da lista.

Ela sai com `role="presentation"`, e não com o `role="separator"` do
`MenuSeparator`. A diferença não é de aparência: dentro de uma lista de opções,
um nó com papel próprio entra na contagem que o leitor de tela anuncia ("opção 3
de 12"), e a conta passa a não bater com o que se vê.

## Quando não usar

Sem `SelectGroup` em volta, ela separa o quê? Numa lista plana a linha vira
divisão sem critério: quem lê procura o motivo do corte e não acha. Se o motivo
existe, ele tem nome, e o nome é um `SelectGroupLabel`.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `orientation` | `Orientation` |  | - | The orientation of the separator. |
| `render` | `ComponentRenderFn<HTMLProps, SelectSeparatorState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` |  | - | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

### SelectTrigger

O campo fechado do select: mostra a escolha e abre a lista.

Tem a mesma altura e a mesma borda do `Input`, para um formulário misto não sair
desalinhado. A seta é desenhada aqui, não passe ícone por fora.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `disabled` | `boolean` |  | 0.4.0 | Whether the component should ignore user interaction. |
| `nativeButton` | `boolean` |  | 0.4.0 | Whether the component renders a native `<button>` element when replacing it via the `render` prop. |
| `render` | `ComponentRenderFn<HTMLProps, SelectTriggerState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` |  | 0.4.0 | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

### SelectValue

O texto do gatilho: o que está escolhido agora.

**Passe `items` com `{ label, value }` no `Select`** para ele mostrar o rótulo.
Sem isso ele mostra o valor cru, porque só a lista sabe traduzir um pelo outro.
É contrato da Base UI, e a armadilha mais fácil de cair neste componente.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `placeholder` | `ReactNode` |  | 0.4.0 | The placeholder value to display when no value is selected. |
| `render` | `ComponentRenderFn<HTMLProps, SelectValueState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` |  | 0.4.0 | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

## Ver também

- [Autocomplete](/componentes/autocomplete.md)
- [Calendar](/componentes/calendar.md)
- [Checkbox](/componentes/checkbox.md)
- [CheckboxGroup](/componentes/checkbox-group.md)
- [ColorPicker](/componentes/color-picker.md)
- [Combobox](/componentes/combobox.md)
- [Convenções da biblioteca](/convencoes.md): Provider, tokens e as regras que valem para toda peça
- [Índice completo](/llms.txt)
