Combobox
Un select filtrable con chips, grupos y resultados asíncronos, en un popup que cambia de tamaño mientras escribes.
pnpm dlx shadcn@latest add https://hextaui.com/r/combobox.jsonAñade el componente, los tokens del tema de HextaUI y los componentes de HextaUI de los que depende.
Añade los tokens del tema a tu CSS global, si aún no lo has hecho.
Instala las dependencias.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopia y pega el siguiente código en tu proyecto.
components/ui/combobox.tsx Actualiza las rutas de importación para que coincidan con la configuración de tu proyecto.
Pasa las opciones a items y renderiza cada una con una función dentro de <ComboboxList />. El combobox las filtra mientras escribes y solo renderiza las coincidencias. Los objetos también funcionan: su label se muestra en el input y su value se envía.
Escribe en el campo para filtrar la lista.
Un botón muestra el valor y el campo de búsqueda pasa al popup.
Con multiple, cada elemento seleccionado se convierte en un chip antes del input.
Botón de borrar
showClear añade un botón de borrar que ocupa el lugar del chevron mientras haya un valor, así que el campo nunca crece.
Con iconos
Los iconos dentro de un elemento se dimensionan y atenúan por ti. autoHighlight resalta la primera coincidencia mientras se escribe, así que Enter la elige.
Grupos y separadores
Pasa grupos con la forma { value, items } y renderiza cada uno con <ComboboxGroup />, <ComboboxLabel /> y <ComboboxCollection />. Los grupos vacíos se ocultan al filtrar.
Múltiple
Con multiple, las selecciones se convierten en chips dentro de <ComboboxChips />. El popup permanece abierto mientras eliges, Backspace en el input vacío quita el último chip, y las teclas de flecha se mueven entre chips.
Búsqueda dentro del popup
Usa <ComboboxTrigger /> para un campo tipo select. Pon el input dentro de <ComboboxContent /> y se convierte en un cuadro de búsqueda con un icono, y el popup se ensancha hasta al menos 15rem.
Trigger renderizado como Button
Pasa render al trigger para usar cualquier botón. El popup se ancla a él y conserva al menos su ancho.
Controlado
Controla la selección con value y onValueChange, y el popup con open y onOpenChange. Borrar establece el valor en null.
Elementos deshabilitados, no válidos y deshabilitados
disabled en la raíz atenúa el campo y sus botones. aria-invalid en el input dibuja el anillo de error. Las teclas de flecha omiten los elementos deshabilitados.
Contenido largo y listas grandes
Las etiquetas largas y sin espacios se ajustan en lugar de ensanchar el popup. limit limita cuántas coincidencias se renderizan, lo que mantiene rápida una lista de 500 elementos.
Búsqueda asíncrona
Desactiva el filtrado integrado con filter={null}, obtén los datos en onInputValueChange y muestra el progreso en <ComboboxStatus />, que lo anuncia a los lectores de pantalla. La altura del popup se anima a medida que cambian los resultados.
Dentro de una hoja
El popup se apila sobre el sheet, y Escape cierra el popup antes que el sheet.
De derecha a izquierda
El popup toma la dirección del campo, así que el botón de borrar, los chips y los elementos se reflejan sin props adicionales.
| Key | Acción |
|---|---|
| ↓↑ | Abre el popup y mueve el resaltado por las coincidencias. Los elementos deshabilitados se omiten. |
| Enter | Elige el elemento resaltado. Sin nada resaltado, cierra el popup y deja que el formulario se envíe. |
| Escape | Cierra el popup. Si ya está cerrado, borra el valor y el input. |
| HomeEnd | Mueve el cursor de texto al inicio o al final del input. |
| Backspace | En un input de chips vacío, quita el último chip. En un chip con foco, lo quita. |
| ←→ | Con chips, mueve el foco entre chips y de vuelta al input. Reflejado en diseños de derecha a izquierda. |
| Tab | Cierra el popup y mueve el foco. |
- Dale al input un
<label>visible medianteidyhtmlFor, o unaria-label. Un<ComboboxTrigger />sin texto visible también necesita unaria-label. - El botón de chevron tiene la etiqueta “Show options”, el de borrar “Clear selection” y el de quitar de cada chip “Remove”.
- El resaltado se mueve con
aria-activedescendant, así que el foco permanece en el input mientras exploras. - Los inputs usan una fuente de 16px en pantallas táctiles para que iOS no haga zoom, y los elementos crecen hasta un objetivo táctil de 44px.
Construido sobre el combobox de Base UI. Cada parte acepta las props de la primitiva que envuelve; las tablas listan las que más usarás.
| Prop | Tipo | Predeterminado |
|---|---|---|
itemsLas opciones. Se filtran mientras escribes y se pasan a la función de renderizado de la lista. | Item[] | Group[] | – |
multipleSelecciona varios valores, mostrados como chips. | boolean | false |
value | Value | Value[] | null | – |
defaultValue | Value | Value[] | null | – |
onValueChange | (value, details) => void | – |
open | boolean | – |
defaultOpen | boolean | false |
onOpenChange | (open: boolean, details) => void | – |
inputValue | string | – |
defaultInputValue | string | – |
onInputValueChange | (inputValue: string, details) => void | – |
filterCoincidencia personalizada. null desactiva el filtrado para la búsqueda en el servidor. | ((item, query, itemToString) => boolean) | null | – |
limitNúmero máximo de coincidencias a renderizar. -1 significa todas. | number | -1 |
autoHighlightResalta la primera coincidencia mientras se escribe. | boolean | false |
highlightItemOnHover | boolean | true |
openOnInputClick | boolean | true |
loopFocusVuelve del último elemento al primero al mover el resaltado. | boolean | true |
itemToStringLabelTexto que se muestra en el input para un elemento de tipo objeto. | (item) => string | – |
itemToStringValueValor que se envía con el formulario para un elemento de tipo objeto. | (item) => string | – |
isItemEqualToValue | (item, value) => boolean | – |
name | string | – |
required | boolean | false |
disabled | boolean | false |
readOnly | boolean | false |
modalBloquea el desplazamiento de la página y los clics exteriores mientras está abierto. | boolean | false |
virtualizedSe define al renderizar elementos con un virtualizador. | boolean | false |
localeConfiguración regional usada para las coincidencias. | Intl.LocalesArgument | – |
Fuera del popup renderiza el campo completo. Dentro de <ComboboxContent /> se convierte en un cuadro de búsqueda compacto.
| Prop | Tipo | Predeterminado |
|---|---|---|
showTriggerMuestra el botón de chevron. Siempre desactivado dentro del popup salvo que se defina. | boolean | true outside the popup |
showClearMuestra un botón de borrar en lugar del chevron mientras haya un valor. | boolean | false |
classNameSe aplica al grupo de input que rodea al input. | string | – |
disabled | boolean | false |
placeholder | string | – |
| Atributo | Descripción |
|---|---|
data-slot="combobox-input-group" | El campo que rodea al input. |
data-slot="combobox-input" | El input de texto. |
data-slot="combobox-input-actions" | Contiene los botones de chevron y de borrar en una sola celda apilada. |
data-popup-open | Presente en el input mientras el popup está abierto. |
data-popup-side | El lado en el que se abrió el popup. |
data-list-empty | Presente cuando nada coincide. |
data-disabled | Presente cuando está deshabilitado. |
data-invalid | Presente cuando no es válido dentro de un Field de Base UI. |
| Prop | Tipo | Predeterminado |
|---|---|---|
childrenNormalmente un <ComboboxValue />. El chevron se añade después. | ReactNode | – |
renderCuando se define, se omiten los estilos de campo integrados. | ReactElement | (props, state) => ReactElement | <button> |
| Atributo | Descripción |
|---|---|
data-slot="combobox-trigger" | El botón trigger. |
data-slot="combobox-trigger-value" | Envuelve el valor truncado. |
data-slot="combobox-trigger-icon" | El chevron. Se voltea mientras está abierto. |
data-popup-open | Presente mientras el popup está abierto. |
data-placeholder | Presente mientras no hay ningún valor seleccionado. |
| Prop | Tipo | Predeterminado |
|---|---|---|
childrenRenderiza tú mismo el valor seleccionado, por ejemplo como chips. | ReactNode | (value) => ReactNode | – |
placeholderSe muestra mientras no hay nada seleccionado. | ReactNode | – |
| Prop | Tipo | Predeterminado |
|---|---|---|
side | "top" | "bottom" | "left" | "right" | "inline-start" | "inline-end" | "bottom" |
align | "start" | "center" | "end" | "start" |
sideOffset | number | 6 |
alignOffset | number | 0 |
anchorPosiciona respecto a otro elemento. Por defecto, el campo. Consulta useComboboxAnchor. | Element | RefObject<Element | null> | VirtualElement | (() => Element | VirtualElement | null) | null | – |
dirPor defecto es la dirección del campo. | "ltr" | "rtl" | – |
| Atributo | Descripción |
|---|---|
data-slot="combobox-positioner" | Posiciona el popup. |
data-slot="combobox-content" | La superficie del popup. |
data-slot="combobox-content-sizer" | Se mide para animar la altura del popup a medida que cambian las coincidencias. |
data-open | Presente mientras está abierto. |
data-side | El lado en el que se abrió. |
data-align | Su alineación. |
data-empty | Presente cuando nada coincide. |
data-starting-style | Presente mientras se anima la entrada. |
data-ending-style | Presente mientras se anima la salida. |
--combobox-item-radius | Radio del elemento, derivado del radio del popup menos su relleno. |
| Prop | Tipo | Predeterminado |
|---|---|---|
childrenSe llama por cada coincidencia de items. | ReactNode | (item, index) => ReactNode | – |
| Atributo | Descripción |
|---|---|
data-slot="combobox-list" | La lista con desplazamiento. |
| Prop | Tipo | Predeterminado |
|---|---|---|
valueEl elemento que representa esta fila. | Item | – |
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
| Atributo | Descripción |
|---|---|
data-slot="combobox-item" | Una opción. |
data-slot="combobox-item-indicator" | La marca, que aparece escalando al seleccionarse. |
data-highlighted | Presente mientras está resaltado. |
data-selected | Presente cuando está seleccionado. |
data-disabled | Presente cuando está deshabilitado. |
| Prop | Tipo | Predeterminado |
|---|---|---|
itemsEn ComboboxGroup: los elementos propios del grupo. | Item[] | – |
childrenEn ComboboxCollection: renderiza cada coincidencia. | (item, index) => ReactNode | – |
| Atributo | Descripción |
|---|---|
data-slot="combobox-group" | Un grupo de elementos. |
data-slot="combobox-label" | El encabezado del grupo. |
<ComboboxEmpty /> muestra a sus hijos solo cuando nada coincide. <ComboboxStatus /> es una región activa para mensajes de carga y de resultados. Ambos colapsan a nada cuando están vacíos.
| Atributo | Descripción |
|---|---|
data-slot="combobox-empty" | El mensaje de sin resultados. |
data-slot="combobox-status" | El mensaje de estado en vivo. |
data-slot="combobox-separator" | Un divisor entre grupos. |
| Prop | Tipo | Predeterminado |
|---|---|---|
children | ReactNode | <IconX /> |
| Atributo | Descripción |
|---|---|
data-slot="combobox-clear" | Etiquetado como “Clear selection”. |
data-visible | Presente mientras hay algo que borrar. |
| Prop | Tipo | Predeterminado |
|---|---|---|
classNameSe aplica al campo que envuelve los chips. | string | – |
| Atributo | Descripción |
|---|---|
data-slot="combobox-chips" | El campo que contiene los chips y el input. |
| Prop | Tipo | Predeterminado |
|---|---|---|
showRemoveMuestra el botón de quitar. | boolean | true |
| Atributo | Descripción |
|---|---|
data-slot="combobox-chip" | Un valor seleccionado. |
data-slot="combobox-chip-label" | Su etiqueta truncada. |
data-slot="combobox-chip-remove" | Etiquetado como “Remove”. |
El input de texto que va después de los chips. Acepta las mismas props que el input de Base UI.
| Atributo | Descripción |
|---|---|
data-slot="combobox-chips-input" | El input de chips. |
useComboboxAnchor()devuelve una ref para pasar a un elemento y aanchoren el contenido.useComboboxFilter()devuelve comparadorescontains,startsWithyendsWithconscientes de la configuración regional parafilter.useComboboxFilteredItems()lee las coincidencias actuales, para contadores o listas virtualizadas.createComboboxItems(data, { getValue })construye una colección de elementos cuyo valor de selección es un id primitivo, como una clave de base de datos, en lugar del objeto completo.comboboxFieldVariantsexpone los estilos del campo para construir campos personalizados.
- CalendarUna cuadrícula de fechas para selección única, de rango y múltiple, con meses deslizantes, vistas previas de rango y días de tamaño táctil.
- CheckboxUna casilla de verificación cuya marca se dibuja, con padres indeterminados, grupos y etiquetas que comparten su hover.
- Date pickerUn botón que abre un calendario en un popover, o en una hoja inferior en móviles, para fechas únicas y rangos.
- FieldEtiquetas, descripciones y errores conectados a su control, con estados de validación y diseños para formularios.
- InputUn campo de texto con tres tamaños, estados inválido y de solo lectura, estilos de validación nativos y una fuente táctil de 16px para que los móviles nunca hagan zoom.
- Input groupUn input con iconos, texto, botones o una pista de teclado acoplados, que comparten un solo borde y anillo de foco.