Skip to content

Headless UI

UI & GraphicsUI FrameworkJavaScript

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/react

Getting started

The smallest useful thing you can do with it, and what each part means.

Dialog (Modal) usage
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>
  );
}
Renders an accessible modal dialog with a title, description, and close button. Styling is fully customizable.
Menu (Dropdown)
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>
Creates an accessible dropdown menu with two options. You can style the button and items as desired.

Advanced usage

Where the library earns its place over a simpler alternative.

Listbox (Select menu)
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>
  );
}
Implements a fully accessible select dropdown using Listbox, with state management for the selected option.
Tabs
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>
  );
}
Creates a tabbed interface with accessible keyboard navigation and ARIA roles managed automatically.

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.