# Combobox

Escolha em lista longa, com busca.

Use quando a lista é grande demais para caber na cabeça de quem escolhe, ou
quando ela vem do servidor.

Compõe com `ComboboxInput`, `ComboboxContent`, `ComboboxList` e
`ComboboxItem`. Lista com famílias de verdade ganha `ComboboxGroup`,
`ComboboxGroupLabel` e `ComboboxSeparator` entre uma família e outra.

Com `multiple`, a escolha vira fichas dentro do próprio campo: `ComboboxChips`
em volta, `ComboboxValue` para saber o que está escolhido e um `ComboboxChip`
por escolha.

## Quando não usar

Com cinco opções fixas, use `Select`: ele custa menos, não pede digitação e não
tem estado de "nada encontrado" para tratar. Busca numa lista que a pessoa
enxerga inteira só acrescenta um teclado no caminho.

Quando o que a pessoa digita **também vale** (uma cidade que não está na lista,
um termo de busca), use `Autocomplete`. Aqui a lista manda: o valor final tem
que ser uma das opções, e texto que não casa com nenhuma se perde ao sair do
campo.

E não use para navegar. Campo com busca que leva a outra tela é `Command`, a
paleta. O combobox devolve um valor a um formulário, e quem escolhe nele espera
que a escolha fique escrita ali, não que a página troque.

## No React Native

Traduz: o `@rivocode/ui-native` exporta `Combobox` - a lista abre numa folha com busca sem acento, e a folha sobe com o teclado; `items` na raiz, não `ComboboxItem` por filho. A API não é a mesma do web (no nativo tudo é controlado), e a [tabela de paridade](/react-native) diz o que muda peça a peça.

## Importação

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

## Exemplos

### Busca em lista

```tsx
import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxContent,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxSeparator,
  ComboboxValue,
} from '@rivocode/ui'

const CLIENTES = [
  { value: 'clinica', label: 'Clínica São Lucas' },
  { value: 'transportes', label: 'Transportes Cabo Branco' },
  { value: 'supermercado', label: 'Supermercado Tambau' },
  { value: 'construtora', label: 'Construtora Litoral' },
]

export function SearchInList() {
  return (
    <div className="min-h-72 w-80">
      <Combobox items={CLIENTES} defaultOpen>
        <ComboboxInput aria-label="Buscar cliente" placeholder="Buscar cliente" />
        <ComboboxContent emptyMessage="Nenhum cliente com esse nome.">
          <ComboboxList>
            {(item: (typeof CLIENTES)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}
```

### Escolha múltipla

```tsx
import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxContent,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxSeparator,
  ComboboxValue,
} from '@rivocode/ui'

const CLIENTES = [
  { value: 'clinica', label: 'Clínica São Lucas' },
  { value: 'transportes', label: 'Transportes Cabo Branco' },
  { value: 'supermercado', label: 'Supermercado Tambau' },
  { value: 'construtora', label: 'Construtora Litoral' },
]

const CIDADES = [
  { value: 'joao-pessoa', label: 'João Pessoa', uf: 'Paraíba' },
  { value: 'campina-grande', label: 'Campina Grande', uf: 'Paraíba' },
  { value: 'recife', label: 'Recife', uf: 'Pernambuco' },
  { value: 'caruaru', label: 'Caruaru', uf: 'Pernambuco' },
]

export function MultipleChoice() {
  return (
    <div className="min-h-72 w-80">
      <Combobox items={CLIENTES} multiple defaultValue={[CLIENTES[0]!, CLIENTES[2]!]}>
        <ComboboxChips>
          <ComboboxValue>
            {(escolhidos: (typeof CLIENTES)[number][]) =>
              escolhidos.map((cliente) => (
                <ComboboxChip key={cliente.value} aria-label={cliente.label}>
                  {cliente.label}
                </ComboboxChip>
              ))
            }
          </ComboboxValue>
          <ComboboxInput aria-label="Buscar cliente" placeholder="Buscar cliente" clearable={false} />
        </ComboboxChips>

        <ComboboxContent emptyMessage="Nenhum cliente com esse nome.">
          <ComboboxList>
            {(item: (typeof CLIENTES)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}

const CIDADES = [
  { value: 'joao-pessoa', label: 'João Pessoa', uf: 'Paraíba' },
  { value: 'campina-grande', label: 'Campina Grande', uf: 'Paraíba' },
  { value: 'recife', label: 'Recife', uf: 'Pernambuco' },
  { value: 'caruaru', label: 'Caruaru', uf: 'Pernambuco' },
]
```

### Lista com famílias

```tsx
import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxContent,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxSeparator,
  ComboboxValue,
} from '@rivocode/ui'

const CIDADES = [
  { value: 'joao-pessoa', label: 'João Pessoa', uf: 'Paraíba' },
  { value: 'campina-grande', label: 'Campina Grande', uf: 'Paraíba' },
  { value: 'recife', label: 'Recife', uf: 'Pernambuco' },
  { value: 'caruaru', label: 'Caruaru', uf: 'Pernambuco' },
]

export function Grouped() {
  return (
    <div className="min-h-80 w-80">
      <Combobox items={CIDADES} defaultOpen>
        <ComboboxInput aria-label="Buscar cidade" placeholder="Buscar cidade" />
        <ComboboxContent emptyMessage="Nenhuma cidade com esse nome.">
          <ComboboxList>
            {/* Agrupar so paga quando as familias sao de verdade. Grupo de dois
                itens acrescenta cabecalho e nao tira trabalho de quem procura -
                e a busca, que e o motivo desta peca existir, ja resolvia. */}
            <ComboboxGroup>
              <ComboboxGroupLabel>Paraíba</ComboboxGroupLabel>
              {CIDADES.filter((c) => c.uf === 'Paraíba').map((c) => (
                <ComboboxItem key={c.value} value={c}>
                  {c.label}
                </ComboboxItem>
              ))}
            </ComboboxGroup>

            <ComboboxSeparator />

            <ComboboxGroup>
              <ComboboxGroupLabel>Pernambuco</ComboboxGroupLabel>
              {CIDADES.filter((c) => c.uf === 'Pernambuco').map((c) => (
                <ComboboxItem key={c.value} value={c}>
                  {c.label}
                </ComboboxItem>
              ))}
            </ComboboxGroup>
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  )
}
```

## Props

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `actionsRef` | `RefObject<Actions \| null>` |  | 0.4.0 | A ref to imperative actions. |
| `autoComplete` | `string` |  | 0.4.0 | Provides a hint to the browser for autofill. |
| `autoHighlight` | `boolean` |  | 0.4.0 | Whether the first matching item is highlighted automatically while filtering. |
| `defaultInputValue` | `string \| number \| readonly string[]` |  | 0.4.0 | The uncontrolled input value when initially rendered. |
| `defaultOpen` | `boolean` |  | 0.4.0 | Whether the popup is initially open. |
| `defaultValue` | `ComboboxValueType<Value, Multiple> \| null` |  | 0.4.0 | The uncontrolled selected value of the combobox when it's initially rendered. |
| `disabled` | `boolean` |  | 0.4.0 | Whether the component should ignore user interaction. |
| `filter` | `((itemValue: Value, query: string, itemToString?: ((itemValue: Value) => string) \| undefined) => boolean) \| null` |  | 0.4.0 | Filter function used to match items vs input query. |
| `filteredItems` | `readonly any[] \| readonly Group<any>[]` |  | 0.4.0 | Filtered items to display in the list. |
| `form` | `string` |  | 0.4.0 | Identifies the form that owns the internal input. |
| `grid` | `boolean` |  | 0.4.0 | Whether list items are presented in a grid layout. |
| `highlightItemOnHover` | `boolean` |  | 0.4.0 | Whether moving the pointer over items should highlight them. |
| `inline` | `boolean` |  | 0.4.0 | Whether the list is rendered inline without using the component's own popup. |
| `inputRef` | `Ref<HTMLInputElement>` |  | 0.4.0 | A ref to the hidden input element. |
| `inputValue` | `string \| number \| readonly string[]` |  | 0.4.0 | The input value of the combobox. |
| `isItemEqualToValue` | `((itemValue: Value, value: Value) => boolean)` |  | 0.4.0 | Custom comparison logic used to determine if a combobox item value matches the current selected value. |
| `items` | `readonly any[] \| readonly Group<any>[]` |  | 0.4.0 | The items to be displayed in the list. |
| `itemToStringLabel` | `((itemValue: Value) => string)` |  | 0.4.0 | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input. |
| `itemToStringValue` | `((itemValue: Value) => string)` |  | 0.4.0 | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for form submission. |
| `limit` | `number` |  | 0.4.0 | The maximum number of items to display in the list. |
| `locale` | `LocalesArgument` |  | 0.4.0 | The locale to use for string comparison. |
| `loopFocus` | `boolean` |  | 0.4.0 | Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. |
| `modal` | `boolean` |  | 0.4.0 | Determines if the popup 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. |
| `onInputValueChange` | `((inputValue: string, eventDetails: ChangeEventDetails) => void)` |  | 0.4.0 | Event handler called when the input value changes. |
| `onItemHighlighted` | `((highlightedValue: Value \| undefined, eventDetails: HighlightEventDetails) => void)` |  | 0.4.0 | Callback fired when an item is highlighted or unhighlighted. |
| `onOpenChange` | `((open: boolean, eventDetails: ChangeEventDetails) => void)` |  | 0.4.0 | Event handler called when the popup is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` |  | 0.4.0 | Event handler called after any animations complete when the popup is opened or closed. |
| `onValueChange` | `((value: ComboboxValueType<Value, Multiple> \| (Multiple extends true ? never : null), eventDetails: ChangeEventDetails) => void)` |  | 0.4.0 | Event handler called when the selected value of the combobox changes. |
| `open` | `boolean` |  | 0.4.0 | Whether the popup is currently open. |
| `openOnInputClick` | `boolean` |  | 0.4.0 | Whether the popup opens when clicking the input. |
| `readOnly` | `boolean` |  | 0.4.0 | Whether the user should be unable to choose a different option from the popup. |
| `required` | `boolean` |  | 0.4.0 | Whether the user must choose a value before submitting a form. |
| `value` | `ComboboxValueType<Value, Multiple> \| null` |  | 0.4.0 | The selected value of the combobox. |
| `virtualized` | `boolean` |  | 0.4.0 | Whether the items are being externally virtualized. |

## Partes

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

### ComboboxChip

Uma escolha, com o xis de tirar.

O xis por dentro diz o que se remove. Quando a ficha é texto, o nome sai pronto
do próprio conteúdo ("Remover Clínica São Lucas"), e quando ela não é (um
`Avatar`, um `Badge`), o `aria-label` da ficha é que responde. Sem um dos dois o
leitor de tela lê uma fila de "Remover, Remover, Remover", e a WCAG 2.4.6 pede
que o nome distinga.

`labels.remove` recebe o texto da ficha e devolve o nome, para trocar o verbo ou
traduzir:

```tsx
<ComboboxChip labels={{ remove: (label) => `Tirar ${label} da seleção` }}>
  Clínica São Lucas
</ComboboxChip>
```

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `labels` | `{ remove?: ((label: string) => string) \| undefined; }` |  | - | O que o leitor de tela ouve no xis. |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxChipState> \| 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. |

### ComboboxChips

A moldura das fichas da escolha múltipla, com o campo de busca dentro dela.

O campo entra como último filho, e não ao lado: as fichas e a digitação são o
mesmo campo aos olhos de quem usa, e separar os dois faz a busca parecer um
filtro de outra coisa.

Dentro dela, o `clearable` do `ComboboxInput` sai de cena: cada ficha já tem o
seu xis, e um limpar geral encostado neles é o botão errado no lugar mais fácil
de acertar sem querer.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxChipsState> \| 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. |

### ComboboxContent

O painel da lista, em portal com o tema. Traz a mensagem de vazio por dentro, em `emptyMessage`.

`side`, `align` e `sideOffset` posicionam o painel, com o mesmo significado e a
mesma folga padrão de 6px do `MenuContent`, do `SelectContent`, do
`PopoverContent` e do `TooltipContent`.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `align` | `Align` |  | - | Alinhamento no eixo do lado escolhido. |
| `emptyMessage` | `ReactNode` |  | 0.4.0 | O que aparece quando a busca nao acha nada. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => void \| boolean \| HTMLElement \| null)` |  | 0.4.0 | Determines the element to focus when the popup is closed. |
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => void \| boolean \| HTMLElement \| null)` |  | 0.4.0 | Determines the element to focus when the popup is opened. |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxPopupState> \| 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. |

### ComboboxGroup

Uma seção da lista, com `ComboboxGroupLabel` de cabeçalho.

Serve para lista longa que tem famílias de verdade: clientes por cidade,
produtos por categoria. Agrupar por agrupar aumenta a altura da lista sem
diminuir a busca, que é justamente o que a peça existe para resolver.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `items` | `readonly any[]` |  | 0.4.0 | Items to be rendered within this group. |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxGroupState> \| 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. |

### ComboboxInput

O campo de busca com o limpar e a seta encostados. Vive dentro do `Combobox`.

O `className` veste a raiz, que aqui é a moldura que segura o campo e os dois
botões, e não o `<input>`. Para alcançar cada uma delas pelo nome, use
`classNames` com as partes `wrapper` e `input`:

```tsx
<ComboboxInput classNames={{ input: "font-mono" }} />
```

É a diferença que separava esta peça do `AutocompleteInput`, que não tem moldura
e por isso veste o próprio campo com o `className`.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `classNames` | `Partial<Record<"input" \| "wrapper", string>>` |  | - | Classe por parte: `wrapper`, `input`. |
| `clearable` | `boolean` |  | 0.4.0 | Mostra o botao de limpar quando ha escolha. |
| `disabled` | `boolean` |  | 0.4.0 | Whether the component should ignore user interaction. |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxInputState> \| 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. |

### ComboboxItem

Uma opção. Mostra o visto quando escolhida.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `disabled` | `boolean` |  | 0.4.0 | Whether the component should ignore user interaction. |
| `index` | `number` |  | 0.4.0 | The index of the item in the list. |
| `nativeButton` | `boolean` |  | 0.4.0 | Whether the component renders a native `<button>` element when replacing it via the `render` prop. |
| `onClick` | `((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)` |  | 0.4.0 | An optional click handler for the item when selected. |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxItemState> \| 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 item. |

### ComboboxList

A lista. Recebe uma funcao que desenha cada item, e não filhos soltos: é assim que a Base UI filtra sem redesenhar tudo.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, ComboboxListState> \| 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. |

### ComboboxSeparator

A linha entre dois `ComboboxGroup` da lista.

É a irmã do `SelectSeparator`, e fecha a paridade com o `MenuSeparator`: as três
listas da biblioteca cortam do mesmo jeito. Como no `Select`, ela sai com
`role="presentation"`: um nó com papel próprio no meio das opções quebraria o
"opção 3 de 12" que o leitor de tela anuncia.

## Quando não usar

Enquanto a busca é o caminho principal, a linha decora e não orienta: quem digita
três letras nunca vê o corte, porque a lista filtrada some com ele. Ela serve à
lista parada, aberta e curta o bastante para ser lida de uma vez, e aí só entre
grupos que têm nome.

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

### ComboboxValue

O que está escolhido, para as fichas saberem o que desenhar.

Não renderiza elemento nenhum: recebe uma função e devolve o que ela montar. É a
peça que faltava para o `ComboboxChips` servir para alguma coisa. Sem ela, a
escolha múltipla com ficha só era possível importando direto da Base UI.

| Prop | Tipo | Obrigatória | Desde | O que faz |
| --- | --- | --- | --- | --- |
| `placeholder` | `ReactNode` |  | 0.5.0 | The placeholder value to display when no value is selected. |

## 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)
- [DatePicker](/componentes/date-picker.md)
- [Convenções da biblioteca](/convencoes.md): Provider, tokens e as regras que valem para toda peça
- [Índice completo](/llms.txt)
