What it is
Headless UI is a set of completely unstyled, accessible UI components for React and Vue. It provides the functionality and accessibility features, leaving styling fully up to you.
Headless UI provides fully accessible interactive components such as Dialog, Menu, Listbox, Tabs, and Transition. These components are unstyled by default, so you can apply any styling framework (commonly Tailwind CSS) or custom CSS.
Installation
npm install @headlessui/reactGetting started
The smallest useful thing you can do with it, and what each part means.
import { Dialog } from '@headlessui/react';
function MyModal({ isOpen, onClose }) {
return (
<Dialog open={isOpen} onClose={onClose}>
<Dialog.Panel className="p-4 bg-white rounded">
<Dialog.Title>My Modal</Dialog.Title>
<Dialog.Description>This is a headless modal.</Dialog.Description>
<button onClick={onClose}>Close</button>
</Dialog.Panel>
</Dialog>
);
}import { Menu } from '@headlessui/react';
<Menu>
<Menu.Button>Options</Menu.Button>
<Menu.Items>
<Menu.Item><button>Edit</button></Menu.Item>
<Menu.Item><button>Delete</button></Menu.Item>
</Menu.Items>
</Menu>Advanced usage
Where the library earns its place over a simpler alternative.
import { Listbox } from '@headlessui/react';
const people = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
function MyListbox() {
const [selected, setSelected] = React.useState(people[0]);
return (
<Listbox value={selected} onChange={setSelected}>
<Listbox.Button>{selected.name}</Listbox.Button>
<Listbox.Options>
{people.map(person => (
<Listbox.Option key={person.id} value={person}>
{person.name}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
);
}import { Tab } from '@headlessui/react';
function MyTabs() {
return (
<Tab.Group>
<Tab.List>
<Tab>Tab 1</Tab>
<Tab>Tab 2</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>Content 1</Tab.Panel>
<Tab.Panel>Content 2</Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Component not rendering correctly
- Ensure you are wrapping items correctly (e.g., `Menu.Item` must be inside `Menu.Items`).
- Focus trap not working in Dialog
- Check that Dialog.Panel is used and your app root is not interfering with focus management.
- Styling not applied
- Headless UI is unstyled by design — add your own CSS or utility classes (e.g., Tailwind).
Best practices
- Pair Headless UI with Tailwind CSS or your preferred styling system for fast UI development.
- Leverage Headless UI components when you need accessibility guarantees without prebuilt styles.
- Use Transition component for smooth animations with modals, menus, and dropdowns.
- Keep accessibility in mind — Headless UI already manages ARIA roles, focus, and keyboard navigation.
- Compose small, reusable UI primitives to match your design system.
Background
Why it exists, and what it was reacting to.
Headless UI was created by the Tailwind Labs team to complement Tailwind CSS. While Tailwind provides utility-first styling, Headless UI provides behavior and accessibility primitives (like modals, menus, and listboxes) without enforcing a specific design. This separation of concerns allows developers to build custom UIs quickly while ensuring accessibility standards.
