
Nada supera construir algo real con tus propias herramientas cuando se trata de encontrar formas de mejorar las cosas.
Mientras hemos estado trabajando en Catalyst estos últimos meses, hemos realizado docenas de mejoras en Headless UI que te permiten escribir aún menos código y mejorar aún más la experiencia del desarrollador.
Acabamos de lanzar Headless UI v2.0 para React, que es la culminación de todo este trabajo.
Aquí están todas las mejores novedades:
- Posicionamiento de anclaje incorporado
- Nuevo componente checkbox
- Componentes de formulario HTML
- Detección mejorada de estado hover, focus y active
- Virtualización de listas Combobox
- Nuevo sitio web y documentación mejorada
Agrégalo a tu proyecto instalando la última versión de @headlessui/react
desde npm:
npm install @headlessui/react@latest
Si estás actualizando desde v1.x, consulta la guía de actualización para saber más sobre lo que ha cambiado.
Posicionamiento de anclaje incorporado
Hemos integrado Floating UI directamente en Headless UI, para que nunca tengas que preocuparte de que los menús desplegables se salgan de la vista o queden ocultos por otros elementos en la pantalla.
Usa la nueva prop anchor
en los componentes Menu
, Popover
, Combobox
y Listbox
para especificar el posicionamiento del anclaje, luego ajusta la ubicación con variables CSS como --anchor-gap
y --anchor-padding
:
Desplázate hacia arriba y hacia abajo para ver cómo cambia la posición del menú desplegable
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";function Example() { return ( <Menu> <MenuButton>Options</MenuButton> <MenuItems anchor="bottom start" className="[--anchor-gap:8px] [--anchor-padding:8px]" > <MenuItem> <button>Edit</button> </MenuItem> <MenuItem> <button>Duplicate</button> </MenuItem> <hr /> <MenuItem> <button>Archive</button> </MenuItem> <MenuItem> <button>Delete</button> </MenuItem> </MenuItems> </Menu> );}
Lo que hace que esta API sea realmente agradable es que puedes ajustar los estilos en diferentes puntos de interrupción cambiando las variables CSS usando clases de utilidad como sm:[--anchor-gap:4px]
.
Consulta la documentación de posicionamiento de anclaje para cada componente para conocer todos los detalles.
Nuevo componente checkbox
Hemos añadido un nuevo componente Checkbox
headless para complementar nuestro componente existente RadioGroup
, facilitando la construcción de controles checkbox totalmente personalizados:
This will give you early access to any awesome new features we're developing.
import { Checkbox, Description, Field, Label } from "@headlessui/react";import { CheckmarkIcon } from "./icons/checkmark";import clsx from "clsx";function Example() { return ( <Field> <Checkbox defaultChecked className={clsx( "size-4 rounded border bg-white dark:bg-white/5", "data-[checked]:border-transparent data-[checked]:bg-blue-500", "focus:outline-none data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500", )} > <CheckmarkIcon className="stroke-white opacity-0 group-data-[checked]:opacity-100" /> </Checkbox> <div> <Label>Enable beta features</Label> <Description>This will give you early access to any awesome new features we're developing.</Description> </div> </Field> );}
Los Checkboxes pueden ser controlados o no controlados, y pueden sincronizar automáticamente su estado con un input oculto para funcionar bien con formularios HTML.
Echa un vistazo a la documentación de Checkbox para saber más.
Componentes de formulario HTML
Hemos añadido un conjunto completamente nuevo de componentes que simplemente envuelven controles de formulario nativos, pero hacen todo el trabajo tedioso de conectar IDs y atributos aria-*
por ti automáticamente.
Así es como se veía construir un campo simple <input>
con un <label>
y una descripción asociados correctamente antes:
<div> <label id="name-label" for="name-input"> Name </label> <input id="name-input" aria-labelledby="name-label" aria-describedby="name-description" /> <p id="name-description">Use your real name so people will recognize you.</p></div>
Y así es como se ve con estos nuevos componentes en Headless UI v2.0:
import { Description, Field, Input, Label } from "@headlessui/react";function Example() { return ( <Field> <Label>Name</Label> <Input name="your_name" /> <Description>Usa tu nombre real para que la gente te reconozca.</Description> </Field> );}
Los nuevos componentes Field
y Fieldset
también propagan los estados deshabilitados como el elemento nativo <fieldset>
, por lo que puedes deshabilitar fácilmente un grupo entero de controles a la vez:
Selecciona un país para ver cómo se habilita el campo de región
import { Button, Description, Field, Fieldset, Input, Label, Legend, Select } from "@headlessui/react";import { regions } from "./countries";export function Example() { const [country, setCountry] = useState(null); return ( <form action="/shipping"> <Fieldset> <Legend>Detalles de envío</Legend> <Field> <Label>Dirección</Label> <Input name="address" /> </Field> <Field> <Label>País</Label> <Description>Actualmente solo enviamos a América del Norte.</Description> <Select name="country" value={country} onChange={(event) => setCountry(event.target.value)}> <option></option> <option>Canadá</option> <option>México</option> <option>Estados Unidos</option> </Select> </Field> <Field disabled={!country}> <Label className="data-[disabled]:opacity-40">Estado/provincia</Label> <Select name="region" className="data-[disabled]:opacity-50"> <option></option> {country && regions[country].map((region) => <option>{region}</option>)} </Select> </Field> <Button>Submit</Button> </Fieldset> </form> );}
Exponemos el estado deshabilitado usando un atributo data-disabled
en el HTML renderizado. Esto nos permite exponerlo incluso en elementos que no admiten el atributo nativo disabled
como el elemento <label>
asociado, lo que facilita mucho el ajuste fino de los estilos deshabilitados para cada elemento.
En total, hemos añadido 8 nuevos componentes aquí — Fieldset
, Legend
, Field
, Label
, Description
, Input
, Select
y Textarea
.
Para más detalles, empieza con la documentación de Fieldset y continúa con el resto.
Detección mejorada de estado hover, focus y active
Usando hooks de la increíble librería React Aria internamente, Headless UI ahora añade atributos de estado data-*
más inteligentes a tus controles que se comportan de manera más consistente en diferentes dispositivos que las pseudoclases CSS nativas:
data-active
— como:active
, pero se elimina al arrastrar fuera del elemento.data-hover
— como:hover
, pero se ignora en dispositivos táctiles para evitar estados hover pegajosos.-
data-focus
— como:focus-visible
, sin falsos positivos por enfoque imperativo.
Click, hover, focus, and drag the button to see the data attributes applied
Para saber más sobre por qué aplicar estos estilos usando JavaScript es importante, te recomiendo encarecidamente leer la excelente serie de blogs de Devon Govett sobre este tema:
- Building a Button Part 1: Press Events
- Building a Button Part 2: Hover Interactions
- Building a Button Part 3: Keyboard Focus Behavior
La web nunca deja de sorprenderme con la cantidad de esfuerzo que se necesita para hacer cosas bonitas de verdad.
Virtualización de listas Combobox
Hemos integrado TanStack Virtual en Headless UI para soportar la virtualización de listas cuando necesitas poner cien mil elementos en tu combobox porque, oye, eso es lo que te dijo el jefe que hicieras.
Usa la nueva prop virtual
para pasar todos tus elementos, y usa la render prop ComboboxOptions
para proporcionar la plantilla para una opción individual:
Abre el combobox y desplázate por las 1,000 opciones
import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";import { ChevronDownIcon } from "@heroicons/react/20/solid";import { useState } from "react";const people = [ { id: 1, name: "Rossie Abernathy" }, { id: 2, name: "Juana Abshire" }, { id: 3, name: "Leonel Abshire" }, { id: 4, name: "Llewellyn Abshire" }, { id: 5, name: "Ramon Abshire" }, // ...hasta 1000 personas];function Example() { const [query, setQuery] = useState(""); const [selected, setSelected] = useState(people[0]); const filteredPeople = query === "" ? people : people.filter((person) => { return person.name.toLowerCase().includes(query.toLowerCase()); }); return ( <Combobox value={selected} virtual={{ options: filteredPeople }} onChange={(value) => setSelected(value)} onClose={() => setQuery("")} > <div> <ComboboxInput displayValue={(person) => person?.name} onChange={(event) => setQuery(event.target.value)} /> <ComboboxButton> <ChevronDownIcon /> </ComboboxButton> </div> <ComboboxOptions> {({ option: person }) => ( <ComboboxOption key={person.id} value={person}> {person.name} </ComboboxOption> )} </ComboboxOptions> </Combobox> );}
Consulta la nueva documentación de virtual scrolling para saber más.
Nuevo sitio web y documentación mejorada
Para acompañar este importante lanzamiento, también hemos renovado significativamente la documentación y le hemos dado una nueva capa de pintura al sitio web:

¡Pásate por el nuevo headlessui.com para echarle un vistazo!