headlessui

安装量: 78
排名: #10007

安装

npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill headlessui

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:

React

npm install @headlessui/react

Vue

npm install @headlessui/vue

Component Catalog Menu (Dropdown)

Accessible dropdown menus with keyboard navigation and ARIA support.

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

function DropdownMenu() { return (

Options

  <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.

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 ( {selected.name}

  <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.

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 ( 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.

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

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

return ( <>

  <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"
            enterTo="opacity-100 scale-100"
            leave="ease-in duration-200"
            leaveFrom="opacity-100 scale-100"
            leaveTo="opacity-0 scale-95"
          >
            <DialogPanel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
              <DialogTitle className="text-lg font-medium leading-6 text-gray-900">
                Payment successful
              </DialogTitle>
              <div className="mt-2">
                <p className="text-sm text-gray-500">
                  Your payment has been successfully submitted.
                </p>
              </div>

              <div className="mt-4">
                <button
                  type="button"
                  className="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
                  onClick={() => setIsOpen(false)}
                >
                  Got it, thanks!
                </button>
              </div>
            </DialogPanel>
          </TransitionChild>
        </div>
      </div>
    </Dialog>
  </Transition>
</>

) }

Dialog Features:

Focus trapping Escape to close Scroll locking Return focus on close Portal rendering Nested dialogs support Initial focus control Popover

Floating panels for tooltips, dropdowns, and more.

import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'

function PopoverExample() { return ( Solutions

  <PopoverPanel
    transition
    anchor="bottom"
    className="divide-y divide-white/5 rounded-xl bg-white/5 text-sm/6 transition duration-200 ease-in-out [--anchor-gap:var(--spacing-5)] data-[closed]:-translate-y-1 data-[closed]:opacity-0"
  >
    <div className="p-3">
      <a className="block rounded-lg py-2 px-3 transition hover:bg-white/5" href="#">
        <p className="font-semibold text-white">Insights</p>
        <p className="text-white/50">Measure actions your users take</p>
      </a>
      <a className="block rounded-lg py-2 px-3 transition hover:bg-white/5" href="#">
        <p className="font-semibold text-white">Automations</p>
        <p className="text-white/50">Create your own targeted content</p>
      </a>
    </div>
  </PopoverPanel>
</Popover>

) }

Popover Features:

Anchor positioning Click or hover triggers Close on click outside Nested popovers Focus management Portal rendering RadioGroup

Accessible radio button groups.

import { RadioGroup, RadioGroupOption, RadioGroupLabel } from '@headlessui/react' import { useState } from 'react'

const plans = [ { name: 'Startup', ram: '12GB', cpus: '6 CPUs', disk: '160 GB SSD disk' }, { name: 'Business', ram: '16GB', cpus: '8 CPUs', disk: '512 GB SSD disk' }, { name: 'Enterprise', ram: '32GB', cpus: '12 CPUs', disk: '1024 GB SSD disk' }, ]

function RadioExample() { const [selected, setSelected] = useState(plans[0])

return ( Server size

{plans.map((plan) => (
{plan.name}
{plan.ram}
{plan.cpus}
{plan.disk}
))}
) }

RadioGroup Features:

Arrow key navigation Disabled options Custom styling states Controlled mode Description support Switch (Toggle)

Accessible toggle switches.

import { Switch } from '@headlessui/react' import { useState } from 'react'

function SwitchExample() { const [enabled, setEnabled] = useState(false)

return ( ) }

Switch Features:

Controlled and uncontrolled Label support Description support Disabled state Keyboard accessible (Space to toggle) Tab (Tabs)

Accessible tab navigation.

import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'

function TabExample() { const categories = [ { name: 'Recent', posts: [ { id: 1, title: 'Does drinking coffee make you smarter?' }, { id: 2, title: "So you've bought coffee... now what?" }, ], }, { name: 'Popular', posts: [ { id: 1, title: 'Is tech making coffee better or worse?' }, { id: 2, title: 'The most innovative things happening in coffee' }, ], }, ]

return ( {categories.map((category) => ( {category.name} ))} {categories.map((category, idx) => (

    {category.posts.map((post) => (
  • {post.title}

  • ))}
))} ) }

Tab Features:

Arrow key navigation Default selected tab Manual activation Vertical/horizontal orientation Controlled mode Disclosure (Accordion)

Expandable content sections.

import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react' import { ChevronUpIcon } from '@heroicons/react/20/solid'

function DisclosureExample() { return ( {({ open }) => ( <> What is your refund policy? ${open ? 'rotate-180 transform' : ''} h-5 w-5 text-purple-500} /> If you're unhappy with your purchase for any reason, email us within 90 days and we'll refund you in full, no questions asked. </> )} ) }

Disclosure Features:

Controlled and uncontrolled Default open state Render props for state access Multiple disclosures (accordion pattern) Smooth animations with Transition Transition

Animation component for enter/leave transitions.

import { Transition } from '@headlessui/react' import { useState } from 'react'

function TransitionExample() { const [isShowing, setIsShowing] = useState(false)

return ( <>

I will fade in and out
</> ) }

Transition Features:

CSS class-based transitions Enter/leave lifecycle Nested transitions (child coordination) Appears support (initial mount animation) Works with React 18 concurrent mode Advanced Patterns Render Props Pattern

Access component state for custom rendering.

import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/react'

function RenderPropsExample() { return ( {({ open }) => ( <> Options {open ? '▲' : '▼'} {({ selected, focus }) => (

{selected && '✓'} Option A
)} </> )} ) }

Controlled Components

Full control over component state.

import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react' import { useState } from 'react'

function ControlledTabs() { const [selectedIndex, setSelectedIndex] = useState(0)

return ( Tab 1 Tab 2 Tab 3 Content 1 Content 2 Content 3 ) }

Portal Rendering

Render components outside DOM hierarchy.

import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react' import { createPortal } from 'react-dom'

function PortalMenu() { return (

Options {createPortal( , document.body )} ) }

Form Integration

Use with form libraries like React Hook Form.

import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/react' import { useForm, Controller } from 'react-hook-form'

function FormExample() { const { control, handleSubmit } = useForm()

const onSubmit = (data) => { console.log(data) }

return (

( Select country United States Canada Mexico )} /> ) }

Vue Support

Headless UI works identically in Vue 3.

<script setup> import { ref } from 'vue' import { Listbox, ListboxButton, ListboxOptions, ListboxOption, } from '@headlessui/vue' const people = [ { id: 1, name: 'Wade Cooper' }, { id: 2, name: 'Arlene Mccoy' }, { id: 3, name: 'Devon Webb' }, ] const selectedPerson = ref(people[0]) </script>

TypeScript Support

Full type safety with TypeScript.

import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react'

interface User { id: number name: string role: 'admin' | 'user' }

interface UserMenuProps { user: User onEdit: (user: User) => void onDelete: (userId: number) => void }

function UserMenu({ user, onEdit, onDelete }: UserMenuProps) { return (

{user.name} {({ focus }) => ( )} {({ focus }) => ( )} ) }

Tailwind CSS Integration

Headless UI is designed for Tailwind CSS.

Data Attributes for States

Headless UI v2 uses data attributes for state styling.

// Modern approach with data attributes Options

// Available states // data-[active] - Element is active/focused // data-[selected] - Element is selected // data-[disabled] - Element is disabled // data-[open] - Element/panel is open // data-[focus] - Element has focus // data-[checked] - Element is checked (Switch)

Tailwind Plugin

Configure Tailwind for Headless UI states.

// tailwind.config.js module.exports = { plugins: [ require('@headlessui/tailwindcss') ] }

Now use modifiers:

Options

Accessibility Features ARIA Attributes

All ARIA attributes managed automatically:

aria-expanded on disclosure buttons aria-selected on tab/option elements aria-checked on switches aria-labelledby for associations aria-describedby for descriptions role attributes (menu, listbox, dialog, etc.) Keyboard Navigation

Full keyboard support built-in:

Arrow keys: Navigate options/tabs Enter/Space: Select/activate Escape: Close menus/dialogs Tab: Focus management Home/End: First/last item (where applicable) Type-ahead: Search by typing Focus Management

Automatic focus handling:

Return focus on close (Dialog, Menu, Popover) Focus trap in modals Initial focus control Skip to focused element on open Screen Reader Support

Tested with:

NVDA (Windows) JAWS (Windows) VoiceOver (macOS, iOS) TalkBack (Android) Server-Side Rendering

Fully compatible with Next.js, Remix, and other SSR frameworks.

// app/page.tsx (Next.js 13+) import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react'

export default function Page() { return (

Options ) }

No special configuration needed - components work identically on server and client.

Best Practices Always provide labels - Use sr-only classes for hidden labels Style all states - Use data attributes for active, selected, disabled states Test keyboard navigation - Verify Tab, arrows, Enter, Escape work Use semantic HTML - Let components render as appropriate elements Provide focus indicators - Always show focus states for keyboard users Test with screen readers - Verify announcements are correct Handle loading states - Show appropriate UI during async operations Use controlled mode when needed - For complex state management Combine with Transition - Add smooth animations to open/close Portal overlays - Use portals for menus/dialogs to avoid z-index issues Common Pitfalls

❌ Missing Tailwind classes for states:

// WRONG - no visual feedback Options

// CORRECT Options

❌ Not using Fragment for render props:

// WRONG - adds extra div

Content

// CORRECT

Content

❌ Forgetting to handle controlled state:

// WRONG - onChange does nothing ...

// CORRECT ...

Resources Documentation: https://headlessui.com GitHub: https://github.com/tailwindlabs/headlessui Examples: https://headlessui.com/react/menu#examples Tailwind UI: Premium components built with Headless UI Summary Headless UI provides unstyled, accessible component primitives Zero runtime CSS - bring your own styles with Tailwind or custom CSS Full accessibility - ARIA, keyboard navigation, screen reader support built-in React and Vue - Identical APIs for both frameworks TypeScript - Complete type definitions included Render props - Access component state for custom rendering SSR compatible - Works with Next.js, Remix, Nuxt Perfect for - Custom design systems, Tailwind CSS integration, accessible components

返回排行榜