Headless UI: Componentes de UI sin estilo y accesibles

Adam Wathan
Logo de Headless UI

Uno de los mayores puntos débiles al construir aplicaciones web modernas es crear componentes personalizados como menús de selección, desplegables, interruptores, modales, pestañas, grupos de radio: componentes que son bastante similares de un proyecto a otro, pero nunca del todo iguales.

Podrías usar un paquete listo para usar, pero generalmente vienen estrechamente acoplados con sus propios estilos proporcionados. Termina siendo muy difícil hacer que coincidan con la apariencia de tu propio proyecto, y casi siempre implica escribir un montón de anulaciones de CSS, lo que se siente como un gran paso atrás cuando trabajas con Tailwind CSS.

La otra opción es construir tus propios componentes desde cero. Al principio parece fácil, pero luego recuerdas que necesitas agregar soporte para la navegación por teclado, administrar atributos ARIA, atrapar el foco, y de repente pasas 3-4 semanas tratando de construir un menú desplegable verdaderamente a prueba de balas.

Creemos que hay una opción mejor, así que la estamos construyendo.

Headless UI es un conjunto de componentes de UI completamente sin estilo y totalmente accesibles para React y Vue (y soon Alpine.js) que facilitan la creación de este tipo de componentes personalizados sin preocuparte por ninguno de los complejos detalles de implementación tú mismo, y sin sacrificar la capacidad de estilizarlos desde cero con clases de utilidad simples.

Así es como se ve construir un desplegable personalizado (uno de los muchos componentes que incluye la librería) usando `@headlessui/react`, con soporte completo para navegación por teclado y gestión de atributos ARIA, estilizado con utilidades simples de Tailwind CSS:

import { Menu } from "@headlessui/react";
function MyDropdown() {
return (
<Menu as="div" className="relative">
<Menu.Button className="rounded bg-blue-600 px-4 py-2 text-white ...">Options</Menu.Button>
<Menu.Items className="absolute right-0 mt-1">
<Menu.Item>
{({ active }) => (
<a className={`${active && "bg-blue-500 text-white"} ...`} href="/account-settings">
Account settings
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a className={`${active && "bg-blue-500 text-white"} ...`} href="/documentation">
Documentation
</a>
)}
</Menu.Item>
<Menu.Item disabled>
<span className="opacity-75 ...">Invite a friend (coming soon!)</span>
</Menu.Item>
</Menu.Items>
</Menu>
);
}

Esto es lo que obtienes gratis en ese ejemplo, sin tener que escribir una sola línea de código relacionada tú mismo:

  • El panel desplegable se abre al hacer clic, con la barra espaciadora, enter o al usar las teclas de flecha
  • El desplegable se cierra cuando presionas escape o haces clic fuera de él
  • Puedes navegar por los elementos usando las teclas de flecha arriba y abajo
  • Puedes saltar al primer elemento usando la tecla `Inicio` y al último elemento usando la tecla `Fin`
  • Los elementos deshabilitados se omiten automáticamente al navegar con el teclado
  • Pasar el cursor sobre un elemento con el mouse después de navegar con el teclado cambiará al enfoque basado en la posición del mouse
  • Los elementos se anuncian correctamente a los lectores de pantalla mientras se navega por ellos con el teclado
  • El botón desplegable se anuncia correctamente a los lectores de pantalla como controlador de un menú
  • ...y probablemente toneladas más que estoy olvidando.

Todo sin escribir las letras `aria` en ninguna parte de tu propio código, y sin escribir un solo detector de eventos. ¡Y aún tienes control total sobre el diseño!

Hay más de 3000 líneas de pruebas para este componente. Bastante bueno que no tuvieras que hacerlo tú mismo, ¿verdad?

Aquí tienes una demostración en vivo completamente estilizada (tomada de Tailwind UI) para que puedas verla en acción:

¡Asegúrate de probarlo con el teclado o un lector de pantalla para apreciarlo realmente!

Acabamos de etiquetar la v0.2.0, que actualmente incluye los siguientes componentes:

Para obtener más información y sumergirte, dirígete al sitio web de Headless UI y lee la documentación.


Si has seguido mi trabajo en línea durante los últimos años, quizás recuerdes mi fascinación por los componentes de UI renderless — algo en lo que realmente comencé a interesarme hacia finales de 2017. He querido que exista una librería como esta durante años, pero hasta que empezamos a hacer crecer el equipo, simplemente no teníamos los recursos para hacerlo realidad.

A principios de este año contratamos a Robin Malfait, y ha estado trabajando en Headless UI a tiempo completo desde entonces.

La mayor motivación para este proyecto es que realmente nos gustaría agregar ejemplos de JS listos para producción a Tailwind UI, que actualmente es un proyecto solo de HTML, del tipo "trae tu propio JavaScript". Esto es genial para muchos de nuestros clientes que desean un control total sobre cómo funciona todo, pero para muchos otros es un punto de fricción.

No queríamos agregar 200 líneas de JS complicado a cada ejemplo de componente, así que empezamos a trabajar en Headless UI como una forma de extraer todo ese ruido, sin renunciar a ninguna flexibilidad en el diseño real de la UI.

¿Por qué reinventar la rueda?

No somos las primeras personas en intentar abordar este problema. Downshift fue la primera librería que vi que me entusiasmó con esta idea en 2017, Reach UI y Reakit comenzaron su desarrollo en 2018, y React Aria se lanzó más recientemente, justo a principios de este año.

Decidimos intentar nuestra propia versión del problema por algunas razones:

  • Las soluciones existentes se centran casi exclusivamente en React, y nos gustaría llevar estas ideas a otros ecosistemas como Vue, Alpine y, con suerte, más en el futuro.
  • Estas librerías serán fundamentales para agregar soporte JS a Tailwind UI, y dado que eso es lo que mantiene el negocio en funcionamiento, nos pareció importante tener un poder de decisión completo sobre cómo funcionaban las librerías y qué soportaban.
  • Tenemos nuestras propias ideas sobre cómo deberían ser las API para estos componentes y queremos poder explorar esas ideas libremente.
  • Queremos asegurarnos de que siempre sea súper fácil estilizar estos componentes con Tailwind, en lugar de tener que escribir CSS personalizado.

Creemos que lo que hemos creado hasta ahora logra un gran equilibrio entre flexibilidad y experiencia del desarrollador, y estamos agradecidos de que haya otras personas trabajando en problemas similares de las que podemos aprender y con las que podemos compartir nuestras ideas.

Qué sigue

Tenemos bastantes componentes más para desarrollar para Headless UI, incluyendo:

  • Modal
  • Grupo de radio
  • Pestañas
  • Acordeón
  • Combobox
  • Selector de fechas

...y probablemente muchos más. También estamos a punto de comenzar con el soporte para Alpine.js, y esperamos poder etiquetar una v1.0 para React, Vue y Alpine cerca de fin de año.

Después de eso, comenzaremos a explorar otros frameworks, con la esperanza de que eventualmente podamos ofrecer las mismas herramientas para ecosistemas como Svelte, Angular y Ember, ya sea de primera clase o con socios de la comunidad.

Si deseas mantenerte al día con lo que estamos haciendo, asegúrate de seguir el proyecto en GitHub.

¿Quieres hablar sobre esta publicación? Discútelo en GitHub →

Recibe todas nuestras actualizaciones directamente en tu bandeja de entrada.
Suscríbete a nuestro boletín.

Copyright © 2025 Tailwind Labs Inc.·Política de Marca Registrada