File: headless-ui-unstyled-accessible-ui-components.md | Updated: 11/15/2025
âKCtrl KDocs Blog Showcase Sponsor Plus
October 6, 2020

Adam Wathan

One of the biggest pain points when building modern web applications is building custom components like select menus, dropdowns, toggles, modals, tabs, radio groups â components that are pretty similar from project to project, but never quite the same.
You could use an off-the-shelf package, but they usually come tightly coupled with their own provided styles. It ends up being very hard to get them to match the look and feel of your own project, and almost always involves writing a bunch of CSS overrides, which feels like a big step backwards when working Tailwind CSS.
The other option is building your own components from scratch. At first it seems easy, but then you remember you need to add support for keyboard navigation, managing ARIA attributes, focus trapping, and all of a sudden you're spending 3-4 weeks trying to build a truly bullet-proof dropdown menu.
We think there's a better option, so we're building it.
Headless UI is a set of completely unstyled, fully accessible UI components for React and Vue (and soon Alpine.js) that make it easy to build these sorts of custom components without worrying about any of the complex implementation details yourself, and without sacrificing the ability to style them from scratch with simple utility classes.
Here's what it looks like to build a custom dropdown (one of many components the library includes) using @headlessui/react, with complete keyboard navigation support and ARIA attribute management, styled with simple Tailwind CSS utilities:
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> );}
Here's what you're getting for free in that example, without having to write a single line of code related to it yourself:
Home key, and the last item using the End keyAll without writing the letters aria anywhere in your own code, and without writing a single event listener. And you still have complete control over the design!
There are over 3000 lines of tests for this component . Pretty nice that you didn't have to do that yourself, right?
Here's a fully-styled live demo (taken from Tailwind UI ) so you can see it in action:
Make sure to try it with the keyboard or a screen reader to really appreciate it!
We just tagged v0.2.0, which currently includes the following components:
To learn more and dive in, head over to the Headless UI website and read the documentation.
If you've followed my work online for the last few years, you might remember my fascination with renderless UI components â something I was really started getting into towards the end of 2017 . I've wanted a library like this to exist for years, but until we started growing the team we just didn't have the resources to make it happen.
Earlier this year we hired Robin Malfait , and he's been working on Headless UI full-time ever since.
The biggest motivation for this project is that we'd really like to add production-ready JS examples to Tailwind UI , which is currently an HTML-only, bring-your-own-JavaScript sort of project. This is great for lots of our customers who want full control over how everything works, but for many others it's a point of friction.
We didn't want to add 200 lines of gnarly JS to every component example, so we started working on Headless UI as a way to extract all of that noise, without giving up any flexibility in the actual UI design.
We're not the first people to try and tackle this problem. Downshift was the first library I saw that got me excited about this idea back in 2017, Reach UI and Reakit started development in 2018, and React Aria was released most recently, just earlier this year.
We decided to try our own take on the problem for a few reasons:
We think what we've come up with so far hits a great balance between flexibility and developer experience, and we're grateful there are other people working on similar problems that we can learn from and share our ideas with.
We've got quite a few more components to develop for Headless UI, including:
...and likely many more. We're also about to start on Alpine.js support, and we're hoping to be able to tag a v1.0 for React, Vue, and Alpine near the end of the year.
After that we'll start exploring other frameworks, with the hope that we can eventually offer the same tools for ecosystems like Svelte, Angular, and Ember, either first-class or with community partners.
If you'd like to keep up with what we're doing, be sure to follow the project on GitHub .
Want to talk about this post? Discuss this on GitHub â
Subscribe
Copyright © 2025 Tailwind Labs Inc.·Trademark Policy