Claude
Skills
Sign in
Back

headlessui

Included with Lifetime
$97 forever

Headless UI - Unstyled, fully accessible UI components for React and Vue with built-in ARIA patterns

universalheadlessuireactvueaccessibilityunstyledprimitivesariatailwind

What this skill does


# Headless UI - Accessible Component Primitives

## Overview

Headless UI provides completely unstyled, fully accessible UI components designed to integrate beautifully with Tailwind CSS. Built by the Tailwind Labs team, it offers production-ready accessibility without imposing design decisions.

**Key Features**:
- Fully unstyled - bring your own styles
- Complete keyboard navigation
- Screen reader tested
- Focus management
- ARIA attributes handled automatically
- TypeScript support
- React 18 and Vue 3 compatible
- SSR compatible
- Render props for maximum flexibility

**Installation**:
```bash
# React
npm install @headlessui/react

# Vue
npm install @headlessui/vue
```

## Component Catalog

### Menu (Dropdown)

Accessible dropdown menus with keyboard navigation and ARIA support.

```tsx
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'

function DropdownMenu() {
  return (
    <Menu>
      <MenuButton className="inline-flex items-center gap-2 rounded-md bg-gray-800 py-1.5 px-3 text-sm/6 font-semibold text-white shadow-inner shadow-white/10 focus:outline-none data-[hover]:bg-gray-700 data-[open]:bg-gray-700 data-[focus]:outline-1 data-[focus]:outline-white">
        Options
        <ChevronDownIcon className="size-4 fill-white/60" />
      </MenuButton>

      <MenuItems
        transition
        anchor="bottom end"
        className="w-52 origin-top-right rounded-xl border border-white/5 bg-white/5 p-1 text-sm/6 text-white transition duration-100 ease-out [--anchor-gap:var(--spacing-1)] focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0"
      >
        <MenuItem>
          <button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
            Edit
          </button>
        </MenuItem>
        <MenuItem>
          <button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
            Duplicate
          </button>
        </MenuItem>
        <div className="my-1 h-px bg-white/5" />
        <MenuItem>
          <button className="group flex w-full items-center gap-2 rounded-lg py-1.5 px-3 data-[focus]:bg-white/10">
            Delete
          </button>
        </MenuItem>
      </MenuItems>
    </Menu>
  )
}
```

**Menu Features**:
- Arrow key navigation
- Type-ahead search
- Automatic focus management
- Escape to close
- Click outside to close
- Portal rendering for positioning
- Anchor positioning API

### Listbox (Select)

Custom select/dropdown component with full keyboard support.

```tsx
import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/react'
import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
import { useState } from 'react'

const people = [
  { id: 1, name: 'Wade Cooper' },
  { id: 2, name: 'Arlene Mccoy' },
  { id: 3, name: 'Devon Webb' },
]

function SelectExample() {
  const [selected, setSelected] = useState(people[0])

  return (
    <Listbox value={selected} onChange={setSelected}>
      <ListboxButton className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm">
        <span className="block truncate">{selected.name}</span>
        <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
          <ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
        </span>
      </ListboxButton>

      <ListboxOptions className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
        {people.map((person) => (
          <ListboxOption
            key={person.id}
            value={person}
            className="relative cursor-default select-none py-2 pl-10 pr-4 data-[focus]:bg-amber-100 data-[focus]:text-amber-900"
          >
            {({ selected }) => (
              <>
                <span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
                  {person.name}
                </span>
                {selected && (
                  <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">
                    <CheckIcon className="h-5 w-5" aria-hidden="true" />
                  </span>
                )}
              </>
            )}
          </ListboxOption>
        ))}
      </ListboxOptions>
    </Listbox>
  )
}
```

**Listbox Features**:
- Single and multiple selection modes
- Type-ahead search
- Arrow key navigation
- Controlled and uncontrolled modes
- Disabled options support
- Custom value comparison

### Combobox (Autocomplete)

Searchable select component with filtering.

```tsx
import { Combobox, ComboboxInput, ComboboxOptions, ComboboxOption } from '@headlessui/react'
import { useState } from 'react'

const people = [
  { id: 1, name: 'Wade Cooper' },
  { id: 2, name: 'Arlene Mccoy' },
  { id: 3, name: 'Devon Webb' },
  { id: 4, name: 'Tom Cook' },
]

function AutocompleteExample() {
  const [selected, setSelected] = useState(people[0])
  const [query, setQuery] = useState('')

  const filtered =
    query === ''
      ? people
      : people.filter((person) =>
          person.name.toLowerCase().includes(query.toLowerCase())
        )

  return (
    <Combobox value={selected} onChange={setSelected}>
      <ComboboxInput
        className="w-full rounded-lg border-none bg-white/5 py-1.5 pr-8 pl-3 text-sm/6 text-white focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25"
        displayValue={(person) => person?.name}
        onChange={(event) => setQuery(event.target.value)}
      />

      <ComboboxOptions className="w-[var(--input-width)] rounded-xl border border-white/5 bg-white/5 p-1 [--anchor-gap:var(--spacing-1)] empty:invisible">
        {filtered.map((person) => (
          <ComboboxOption
            key={person.id}
            value={person}
            className="group flex cursor-default items-center gap-2 rounded-lg py-1.5 px-3 select-none data-[focus]:bg-white/10"
          >
            <CheckIcon className="invisible size-4 fill-white group-data-[selected]:visible" />
            <div className="text-sm/6 text-white">{person.name}</div>
          </ComboboxOption>
        ))}
      </ComboboxOptions>
    </Combobox>
  )
}
```

**Combobox Features**:
- Text input with filtering
- Keyboard navigation
- Nullable/optional selections
- Custom display values
- Async data loading support
- Multiple selection mode

### Dialog (Modal)

Accessible modal dialogs with focus trapping.

```tsx
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
import { Fragment, useState } from 'react'

function ModalExample() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open dialog</button>

      <Transition appear show={isOpen} as={Fragment}>
        <Dialog as="div" className="relative z-10" onClose={() => setIsOpen(false)}>
          <TransitionChild
            as={Fragment}
            enter="ease-out duration-300"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="ease-in duration-200"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <div className="fixed inset-0 bg-black/25" />
          </TransitionChild>

          <div className="fixed inset-0 overflow-y-auto">
            <div className="flex min-h-full items-center justify-center p-4 text-center">
              <TransitionChild
                as={Fragment}
                enter="ease-out duration-300"
                enterFrom="opacity-0 scale-95"
              

Related in universal