Headless UI helps you build components without styling so you can add your own look easily. It gives you the structure and behavior, but you control the design.
Component library patterns (Headless UI) in Tailwind
import { Menu } from '@headlessui/react'
<Menu>
<Menu.Button>Options</Menu.Button>
<Menu.Items>
<Menu.Item>
{({ active }) => (
<a className={`${active ? 'bg-blue-500 text-white' : 'text-gray-900'}`} href="#">
Account settings
</a>
)}
</Menu.Item>
</Menu.Items>
</Menu>import { Dialog } from '@headlessui/react'
<Dialog open={isOpen} onClose={setIsOpen}>
<Dialog.Overlay />
<Dialog.Title>Deactivate account</Dialog.Title>
<Dialog.Description>
This will permanently deactivate your account
</Dialog.Description>
<button onClick={() => setIsOpen(false)}>Close</button>
</Dialog>import { Tab } from '@headlessui/react'
<Tab.Group>
<Tab.List>
<Tab>First</Tab>
<Tab>Second</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>Content for first tab</Tab.Panel>
<Tab.Panel>Content for second tab</Tab.Panel>
</Tab.Panels>
</Tab.Group>This example shows a simple dropdown menu using Headless UI patterns with Tailwind CSS styling. The menu button toggles the menu items. The menu is keyboard accessible and closes when clicking outside.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Headless UI Menu Example</title> <script src="https://cdn.tailwindcss.com"></script> <script type="module"> window.addEventListener('DOMContentLoaded', () => { const menuButton = document.getElementById('menu-button') const menuItems = document.getElementById('menu-items') menuButton.addEventListener('click', (event) => { event.stopPropagation(); const expanded = menuButton.getAttribute('aria-expanded') === 'true' menuButton.setAttribute('aria-expanded', String(!expanded)) menuItems.classList.toggle('hidden') }) // Close menu when clicking outside document.addEventListener('click', (event) => { if (!menuButton.contains(event.target) && !menuItems.contains(event.target)) { menuButton.setAttribute('aria-expanded', 'false') menuItems.classList.add('hidden') } }) }) </script> </head> <body class="flex items-center justify-center min-h-screen bg-gray-100"> <div class="relative inline-block text-left"> <button id="menu-button" aria-haspopup="true" aria-expanded="false" class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> Options <svg class="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> </svg> </button> <div id="menu-items" class="hidden origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1"> <div class="py-1" role="none"> <a href="#" class="text-gray-700 block px-4 py-2 text-sm hover:bg-indigo-600 hover:text-white" role="menuitem" tabindex="-1">Account settings</a> <a href="#" class="text-gray-700 block px-4 py-2 text-sm hover:bg-indigo-600 hover:text-white" role="menuitem" tabindex="-1">Support</a> <a href="#" class="text-gray-700 block px-4 py-2 text-sm hover:bg-indigo-600 hover:text-white" role="menuitem" tabindex="-1">License</a> </div> </div> </div> </body> </html>
Headless UI components provide accessibility features like keyboard navigation and ARIA roles automatically.
You style the components yourself using Tailwind CSS or any other method, so your design stays consistent.
Headless UI works well with React, but you can implement similar patterns manually in plain HTML/JS as shown.
Headless UI gives you the behavior and accessibility of components without styling.
You add your own styles using Tailwind CSS or other CSS tools.
This helps build flexible, accessible, and reusable UI parts easily.
Practice
Solution
Step 1: Understand Headless UI's role
Headless UI provides the logic and accessibility for components but no styles.Step 2: Compare with styling tools
Styling is added separately, often with Tailwind CSS, so Headless UI focuses on behavior only.Final Answer:
To provide accessible component behavior without styling -> Option DQuick Check:
Headless UI = behavior only [OK]
- Thinking Headless UI provides styles
- Confusing Headless UI with Tailwind CSS
- Assuming it creates animations automatically
Solution
Step 1: Identify the correct package name
The official Headless UI React package is '@headlessui/react'.Step 2: Use named import for components
Components like Menu are imported as named imports from '@headlessui/react'.Final Answer:
import { Menu } from '@headlessui/react'; -> Option AQuick Check:
Correct import syntax = import { Menu } from '@headlessui/react'; [OK]
- Using default import instead of named import
- Importing from 'tailwindcss' instead of '@headlessui/react'
- Using wrong package names like 'headless-ui' or '@headlessui/css'
Menu component, what will be the visible output when the button is clicked?import { Menu } from '@headlessui/react';
export default function Example() {
return (
);
}Solution
Step 1: Understand Menu component behavior
The Menu.Button toggles visibility of Menu.Items as a dropdown.Step 2: Check rendering of Menu.Items and Menu.Item
Menu.Items contains two Menu.Item links that show with styling on hover (active).Final Answer:
A button labeled 'Options' that toggles a dropdown with 'Account' and 'Settings' links -> Option BQuick Check:
Menu.Button toggles dropdown = A button labeled 'Options' that toggles a dropdown with 'Account' and 'Settings' links [OK]
- Thinking dropdown is always visible
- Assuming Menu.Items needs extra wrapper
- Expecting error due to JSX structure
Disclosure usage in React?import { Disclosure } from '@headlessui/react';
export default function Example() {
return (
<Disclosure>
<Disclosure.Button>Toggle</Disclosure.Button>
<div>Hidden content</div>
</Disclosure>
);
}Solution
Step 1: Check Disclosure structure
Disclosure requires hidden content insideDisclosure.Panelfor toggle functionality.Step 2: Identify missing wrapper
The<div>Hidden content</div>should be replaced with<Disclosure.Panel>Hidden content</Disclosure.Panel>.Final Answer:
The hidden content should be wrapped in Disclosure.Panel for proper behavior -> Option AQuick Check:
Disclosure.Panel wraps hidden content [OK]
- Placing hidden content directly without Disclosure.Panel
- Moving Disclosure.Button outside Disclosure
- Incorrect import or missing hooks
Menu component styled with Tailwind CSS. You want the menu items to close the menu when clicked and highlight on keyboard navigation. Which approach correctly combines Headless UI behavior with Tailwind styling?Solution
Step 1: Use Headless UI components for behavior
Menu.Button toggles menu visibility; Menu.Items contains menu options; Menu.Item handles keyboard navigation and active state.Step 2: Apply Tailwind CSS for styling active states
Use the active render prop in Menu.Item to conditionally apply Tailwind classes for highlight on keyboard focus or hover.Step 3: Ensure menu closes on item click
Headless UI automatically closes the menu when a Menu.Item is clicked, preserving accessibility.Final Answer:
Use Menu.Button to toggle, wrap items in Menu.Items, and style Menu.Item using the active state with Tailwind classes -> Option CQuick Check:
Headless UI + Tailwind styling = UseMenu.Buttonto toggle, wrap items inMenu.Items, and styleMenu.Itemusing the active state with Tailwind classes [OK]
- Styling only Menu.Button and ignoring Menu.Item active states
- Not using Menu.Item components for menu options
- Trying to build dropdown without Menu.Button
Ready to prove it?
Pass the test and this pattern moves unknown
