Headless UI - Accessible UI Components for React and Vue

repository·main·Indexed Apr 15, 2026

https://github.com/tailwindlabs/headlessui

Headless UI is a collection of completely unstyled, fully accessible UI components for React and Vue that integrate seamlessly with Tailwind CSS. It handles complex state management, focus trapping, and ARIA attributes, leaving all visual styling to the developer. The repository includes @headlessui/react, @headlessui/vue, and @headlessui/tailwindcss. Features include new form components (Checkbox, Input, Select), data-attribute-based transitions, React 19 support, and performance improvements for Menu, Listbox, and Combobox.

Tokens
62K
Snippets
151
Records
456
Agent score
99%

What's inside headlessui

  1. Usage

    main
    <script setup>
    import { Switch, SwitchLabel, SwitchDescription } from '@headlessui/vue'
    
    const enabled = true
    </script>
    
    <template>
      <Switch v-model="enabled">
        <SwitchLabel>Enable Dark Mode</SwitchLabel>
        <SwitchDescription>
          Switch between light and dark themes.
        </SwitchDescription>
      </Switch>
    </template>
  2. Use simplified data-* attributes for styling

    main

    Headless UI now provides simplified data-* attributes as an alternative to the existing data-headlessui-state="..." attributes. These new attributes make it easier to style components based on their state using Tailwind CSS.

    New Attributes:

    • data-autofocus: Maps to the autoFocus prop on focusable components.
    • data-* attributes for state-based styling (e.g., data-open, data-closed, data-checked, data-unchecked).

    Usage: You can now use these attributes directly in your Tailwind classes:

    <div data-open="" data-closed="">
      <span data-open="" class="text-green-500">Open</span>
      <span data-closed="" class="text-red-500">Closed</span>
    </div>
    <div data-open="" data-closed="">
      <span data-open="" class="text-green-500">Open</span>
      <span data-closed="" class="text-red-500">Closed</span>
    </div>

    Sources: packages/@headlessui-react/CHANGELOG.md

  3. Use new form components (Checkbox, Input, Select, etc.)

    main

    The library now includes dedicated form components that simplify building accessible forms. These components handle their own state and integrate with the Field system for labels and descriptions.

    Available Components:

    • Checkbox
    • Radio (alternative to RadioGroup.Option)
    • Button
    • Input
    • Textarea
    • Select
    • Field, Label, Description, Fieldset, Legend
    • DataInteractive

    Usage Pattern: Wrap your form elements in Field components to automatically handle Label and Description association. Use the form prop on components like RadioGroup, Switch, Listbox, and Combobox to associate them with a specific form.

    import { Field, Label, Input, Checkbox } from '@headlessui/react'
    
    function MyForm() {
      return (
        <form>
          <Field>
            <Label>Email</Label>
            <Input type="email" />
          </Field>
    
          <Field>
            <Label>
              <Checkbox />
              Subscribe to newsletter
            </Label>
          </Field>
        </form>
      )
    }
    import { Field, Label, Input, Checkbox } from '@headlessui/react'
    
    function MyForm() {
      return (
        <form>
          <Field>
            <Label>Email</Label>
            <Input type="email" />
          </Field>
    
          <Field>
            <Label>
              <Checkbox />
              Subscribe to newsletter
            </Label>
          </Field>
        </form>
      )
    }

    Sources: packages/@headlessui-react/CHANGELOG.md

  4. Use new MenuSection, MenuHeading, and MenuSeparator components

    main

    The Menu component now includes dedicated sub-components for better organization of menu items:

    • MenuSection: Groups menu items into sections.
    • MenuHeading: Adds a heading within a menu section.
    • MenuSeparator: Adds a visual separator between menu items.

    Usage:

    import { Menu, MenuButton, MenuItem, MenuSection, MenuHeading, MenuSeparator } from '@headlessui/react'
    
    function MyMenu() {
      return (
        <Menu>
          <MenuButton>Menu</MenuButton>
          <MenuSection>
            <MenuHeading>Settings</MenuHeading>
            <MenuItem>Profile</MenuItem>
            <MenuItem>Account</MenuItem>
            <MenuSeparator />
            <MenuItem>Logout</MenuItem>
          </MenuSection>
        </Menu>
      )
    }
    import { Menu, MenuButton, MenuItem, MenuSection, MenuHeading, MenuSeparator } from '@headlessui/react'
    
    function MyMenu() {
      return (
        <Menu>
          <MenuButton>Menu</MenuButton>
          <MenuSection>
            <MenuHeading>Settings</MenuHeading>
            <MenuItem>Profile</MenuItem>
            <MenuItem>Account</MenuItem>
            <MenuSeparator />
            <MenuItem>Logout</MenuItem>
          </MenuSection>
        </Menu>
      )
    }

    Sources: packages/@headlessui-react/CHANGELOG.md

  5. Install and configure the Tailwind CSS plugin

    main

    The @headlessui/tailwindcss package provides state-based variants for styling Headless UI components dynamically using Tailwind CSS. To use it, install the package and register the plugin in your tailwind.config.js file.

    Installation

    npm install @headlessui/tailwindcss

    Configuration Add the plugin to your tailwind.config.js:

    // tailwind.config.js
    import headlessuiPlugin from '@headlessui/tailwindcss'
    
    export default {
      content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue}'],
      theme: {
        extend: {},
      },
      plugins: [
        headlessuiPlugin(),
      ],
    }

    Custom Prefix By default, the plugin uses the ui prefix (e.g., ui-open, ui-checked). You can customize this by passing an options object:

    // tailwind.config.js
    import headlessuiPlugin from '@headlessui/tailwindcss'
    
    export default {
      // ... other config
      plugins: [
        headlessuiPlugin({ prefix: 'headless' }),
      ],
    }

    With a custom prefix, you would use classes like headless-open:underline instead of ui-open:underline.

    Sources: packages/@headlessui-tailwindcss/src/index.ts

  6. Install and configure @headlessui/tailwindcss

    main

    Install the plugin and add it to your Tailwind configuration to enable state-based styling variants for Headless UI components.

    1. Install the package:
    npm install @headlessui/tailwindcss
    1. Add the plugin to your tailwind.config.js:
    module.exports = {
      content: [],
      theme: {
        extend: {},
      },
      plugins: [
        require('@headlessui/tailwindcss')
        // Or with a custom prefix:
        // require('@headlessui/tailwindcss')({ prefix: 'ui' })
      ],
    }

    Optionally, pass an options object to require to set a custom prefix for all variants (e.g., { prefix: 'ui' } changes ui-open to ui-open with the specified prefix applied to utility classes if needed, though the variants themselves remain ui-* by default).

    npm install @headlessui/tailwindcss
    
    // tailwind.config.js
    module.exports = {
      content: [],
      theme: {
        extend: {},
      },
      plugins: [
        require('@headlessui/tailwindcss')
      ],
    }

    Sources: packages/@headlessui-tailwindcss/README.md

  7. Usage

    main

    Wrap your Switch components inside a SwitchGroup. The group automatically handles the labelledby and describedby ARIA attributes for child switches.

    <script setup>
    import { Switch, SwitchGroup, SwitchLabel, SwitchDescription } from '@headlessui/vue'
    
    const switch1 = false
    const switch2 = true
    </script>
    
    <template>
      <SwitchGroup>
        <SwitchLabel>Notification Settings</SwitchLabel>
        <SwitchDescription>
          Manage how you receive notifications.
        </SwitchDescription>
    
        <Switch v-model="switch1" class="...">
          Email Notifications
        </Switch>
    
        <Switch v-model="switch2" class="...">
          Push Notifications
        </Switch>
      </SwitchGroup>
    </template>
  8. Fix Combobox blur behavior

    main

    A fix ensures that blurring the Combobox.Input component now correctly closes the Combobox. This improves the user experience by automatically closing the dropdown when the user clicks away from the input field.

    Behavior:

    • When the Combobox.Input loses focus, the Combobox will close.
    • This behavior is consistent with standard dropdown patterns.

    Usage: No additional configuration is needed. The fix is applied automatically when using the Combobox component.

    import { Combobox, ComboboxInput } from '@headlessui/react'
    
    function MyCombobox() {
      return (
        <Combobox>
          <ComboboxInput />
          <ComboboxOptions>
            {/* Options */}
          </ComboboxOptions>
        </Combobox>
      )
    }
    import { Combobox, ComboboxInput } from '@headlessui/react'
    
    function MyCombobox() {
      return (
        <Combobox>
          <ComboboxInput />
          <ComboboxOptions>
            {/* Options */}
          </ComboboxOptions>
        </Combobox>
      )
    }

    Sources: packages/@headlessui-react/CHANGELOG.md

  9. Configure Combobox with new props (anchor, virtual, immediate)

    main

    The Combobox component now supports advanced configuration props for better control over behavior and rendering.

    New Props:

    • immediate: When set to true, the Combobox opens immediately when the input receives focus, without requiring a click.
    • virtual: Enables virtualization mode for large lists, allowing you to render only visible options.
    • anchor: Allows you to specify a custom anchor element for the dropdown panel.

    Usage:

    import { Combobox } from '@headlessui/react'
    
    function MyCombobox() {
      return (
        <Combobox
          immediate={true}
          virtual={true}
          anchor={document.getElementById('custom-anchor')}
        >
          {/* Your Combobox content */}
        </Combobox>
      )
    }
    import { Combobox } from '@headlessui/react'
    
    function MyCombobox() {
      return (
        <Combobox
          immediate={true}
          virtual={true}
          anchor={document.getElementById('custom-anchor')}
        >
          {/* Your Combobox content */}
        </Combobox>
      )
    }

    Sources: packages/@headlessui-react/CHANGELOG.md