Components for organizing and navigating content including dropdown menus, tab interfaces, and collapsible disclosure panels.
A dropdown menu component for navigation and actions with keyboard navigation and accessibility.
/**
* Dropdown menu component for actions and navigation
* @param props - Menu properties
*/
function Menu<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: MenuProps<TTag>
): JSX.Element;
interface MenuProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Demo mode flag for development */
__demoMode?: boolean;
/** Render prop providing menu state */
children?: React.ReactNode | ((props: MenuRenderProps) => React.ReactNode);
}
interface MenuRenderProps {
/** Whether menu is open */
open: boolean;
/** Function to close menu */
close: () => void;
}Button to toggle the menu display.
/**
* Button to toggle the menu
* @param props - MenuButton properties
*/
function MenuButton<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: MenuButtonProps<TTag>
): JSX.Element;
interface MenuButtonProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends PolymorphicProps<TTag> {
/** Whether button is disabled */
disabled?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing button state */
children?: React.ReactNode | ((props: MenuButtonRenderProps) => React.ReactNode);
}
interface MenuButtonRenderProps {
/** Whether menu is open */
open: boolean;
/** Whether button is active/pressed */
active: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether disabled */
disabled: boolean;
/** Whether has autofocus */
autofocus: boolean;
}Container for menu items with keyboard navigation.
/**
* Container for menu items
* @param props - MenuItems properties
*/
function MenuItems<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: MenuItemsProps<TTag>
): JSX.Element;
interface MenuItemsProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether to hold focus on items */
hold?: boolean;
/** Floating UI anchor configuration */
anchor?: AnchorProps;
/** Whether to render in portal */
portal?: boolean;
/** Whether to use modal behavior */
modal?: boolean;
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing items state */
children?: React.ReactNode | ((props: MenuItemsRenderProps) => React.ReactNode);
}
interface MenuItemsRenderProps {
/** Whether menu is open */
open: boolean;
}Individual item within the menu.
/**
* Individual item within the menu
* @param props - MenuItem properties
*/
function MenuItem<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: MenuItemProps<TTag>
): JSX.Element;
interface MenuItemProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether item is disabled */
disabled?: boolean;
/** Display order */
order?: number;
/** Render prop providing item state */
children?: React.ReactNode | ((props: MenuItemRenderProps) => React.ReactNode);
}
interface MenuItemRenderProps {
/** Whether item has focus */
focus: boolean;
/** Whether item is active (deprecated, use focus) */
active: boolean;
/** Whether item is disabled */
disabled: boolean;
/** Function to close menu */
close: () => void;
}Heading component for menu sections.
/**
* Heading for menu sections
* @param props - MenuHeading properties
*/
function MenuHeading<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: MenuHeadingProps<TTag>
): JSX.Element;
interface MenuHeadingProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Standard heading properties */
}Section grouping component for menu items.
/**
* Section grouping for menu items
* @param props - MenuSection properties
*/
function MenuSection<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: MenuSectionProps<TTag>
): JSX.Element;
interface MenuSectionProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Standard section properties */
}Visual separator between menu items or sections.
/**
* Visual separator between menu items
* @param props - MenuSeparator properties
*/
function MenuSeparator<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: MenuSeparatorProps<TTag>
): JSX.Element;
interface MenuSeparatorProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Standard separator properties */
}Usage Examples:
import {
Menu,
MenuButton,
MenuItems,
MenuItem,
MenuHeading,
MenuSection,
MenuSeparator
} from "@headlessui/react";
function BasicMenuExample() {
return (
<Menu>
<MenuButton className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Options
</MenuButton>
<MenuItems
anchor="bottom end"
className="w-52 bg-white border border-gray-200 rounded-lg shadow-lg p-1 mt-1"
>
<MenuItem>
{({ focus }) => (
<button
className={`${focus ? 'bg-blue-500 text-white' : 'text-gray-900'}
group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
Edit
</button>
)}
</MenuItem>
<MenuItem>
{({ focus }) => (
<button
className={`${focus ? 'bg-blue-500 text-white' : 'text-gray-900'}
group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
Duplicate
</button>
)}
</MenuItem>
<MenuSeparator className="my-1 h-px bg-gray-200" />
<MenuItem disabled>
{({ focus, disabled }) => (
<button
className={`${disabled ? 'text-gray-400' : focus ? 'bg-red-500 text-white' : 'text-red-600'}
group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
Delete (disabled)
</button>
)}
</MenuItem>
</MenuItems>
</Menu>
);
}
// Complex menu with sections and headings
function ComplexMenuExample() {
return (
<Menu>
<MenuButton className="flex items-center space-x-2 px-4 py-2 border border-gray-300 rounded">
<img className="w-8 h-8 rounded-full" src="/profile.jpg" alt="Profile" />
<span>John Doe</span>
<ChevronDownIcon className="w-4 h-4" />
</MenuButton>
<MenuItems
anchor="bottom end"
className="w-64 bg-white border border-gray-200 rounded-lg shadow-lg p-2 mt-2"
>
<MenuSection>
<MenuHeading className="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider">
Account
</MenuHeading>
<MenuItem>
{({ focus, close }) => (
<a
href="/profile"
onClick={() => close()}
className={`${focus ? 'bg-gray-100' : ''}
flex items-center px-3 py-2 text-sm text-gray-700 rounded-md`}
>
<UserIcon className="w-4 h-4 mr-3" />
Your Profile
</a>
)}
</MenuItem>
<MenuItem>
{({ focus, close }) => (
<a
href="/settings"
onClick={() => close()}
className={`${focus ? 'bg-gray-100' : ''}
flex items-center px-3 py-2 text-sm text-gray-700 rounded-md`}
>
<CogIcon className="w-4 h-4 mr-3" />
Settings
</a>
)}
</MenuItem>
</MenuSection>
<MenuSeparator className="my-2 h-px bg-gray-200" />
<MenuSection>
<MenuHeading className="px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wider">
Actions
</MenuHeading>
<MenuItem>
{({ focus, close }) => (
<button
onClick={() => {
console.log('Signing out...');
close();
}}
className={`${focus ? 'bg-red-50 text-red-700' : 'text-red-600'}
flex items-center w-full px-3 py-2 text-sm rounded-md`}
>
<ArrowRightOnRectangleIcon className="w-4 h-4 mr-3" />
Sign out
</button>
)}
</MenuItem>
</MenuSection>
</MenuItems>
</Menu>
);
}A tab interface component for organizing content into multiple panels.
/**
* Tab interface for organizing content
* @param props - TabGroup properties
*/
function TabGroup<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: TabGroupProps<TTag>
): JSX.Element;
interface TabGroupProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Currently selected tab index */
selectedIndex?: number;
/** Default selected index for uncontrolled usage */
defaultIndex?: number;
/** Selection change handler */
onChange?: (index: number) => void;
/** Whether tabs are vertically oriented */
vertical?: boolean;
/** Whether tab selection requires manual activation (not automatic on focus) */
manual?: boolean;
/** Render prop providing tab group state */
children?: React.ReactNode | ((props: TabGroupRenderProps) => React.ReactNode);
}
interface TabGroupRenderProps {
/** Currently selected tab index */
selectedIndex: number;
}Container for tab buttons.
/**
* Container for tab buttons
* @param props - TabList properties
*/
function TabList<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: TabListProps<TTag>
): JSX.Element;
interface TabListProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Render prop providing tab list state */
children?: React.ReactNode | ((props: TabListRenderProps) => React.ReactNode);
}
interface TabListRenderProps {
/** Currently selected tab index */
selectedIndex: number;
}Individual tab button.
/**
* Individual tab button
* @param props - Tab properties
*/
function Tab<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: TabProps<TTag>
): JSX.Element;
interface TabProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends PolymorphicProps<TTag> {
/** Whether tab is disabled */
disabled?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing tab state */
children?: React.ReactNode | ((props: TabRenderProps) => React.ReactNode);
}
interface TabRenderProps {
/** Whether tab is selected */
selected: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether has focus */
focus: boolean;
/** Whether being pressed */
active: boolean;
/** Whether has autofocus */
autofocus: boolean;
/** Whether disabled */
disabled: boolean;
}Container for tab panel content.
/**
* Container for tab panels
* @param props - TabPanels properties
*/
function TabPanels<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: TabPanelsProps<TTag>
): JSX.Element;
interface TabPanelsProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Render prop providing tab panels state */
children?: React.ReactNode | ((props: TabPanelsRenderProps) => React.ReactNode);
}
interface TabPanelsRenderProps {
/** Currently selected tab index */
selectedIndex: number;
}Individual tab panel content.
/**
* Individual tab panel content
* @param props - TabPanel properties
*/
function TabPanel<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: TabPanelProps<TTag>
): JSX.Element;
interface TabPanelProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Tab index for keyboard navigation */
tabIndex?: number;
/** Render prop providing panel state */
children?: React.ReactNode | ((props: TabPanelRenderProps) => React.ReactNode);
}
interface TabPanelRenderProps {
/** Whether panel is selected/visible */
selected: boolean;
/** Whether panel has focus */
focus: boolean;
}Usage Examples:
import { useState } from "react";
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from "@headlessui/react";
function BasicTabsExample() {
const categories = ['Recent', 'Popular', 'Trending'];
return (
<div className="w-full max-w-md mx-auto">
<TabGroup>
<TabList className="flex space-x-1 rounded-xl bg-blue-900/20 p-1">
{categories.map((category) => (
<Tab
key={category}
className={({ selected }) =>
`w-full rounded-lg py-2.5 text-sm font-medium leading-5
ring-white/60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2
${selected
? 'bg-white text-blue-700 shadow'
: 'text-blue-100 hover:bg-white/[0.12] hover:text-white'
}`
}
>
{category}
</Tab>
))}
</TabList>
<TabPanels className="mt-2">
<TabPanel className="rounded-xl bg-white p-3">
<h3 className="text-lg font-semibold">Recent Posts</h3>
<p className="mt-2 text-gray-600">Content for recent posts...</p>
</TabPanel>
<TabPanel className="rounded-xl bg-white p-3">
<h3 className="text-lg font-semibold">Popular Posts</h3>
<p className="mt-2 text-gray-600">Content for popular posts...</p>
</TabPanel>
<TabPanel className="rounded-xl bg-white p-3">
<h3 className="text-lg font-semibold">Trending Posts</h3>
<p className="mt-2 text-gray-600">Content for trending posts...</p>
</TabPanel>
</TabPanels>
</TabGroup>
</div>
);
}
// Controlled tabs
function ControlledTabsExample() {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<TabGroup selectedIndex={selectedIndex} onChange={setSelectedIndex}>
<TabList className="flex border-b border-gray-200">
<Tab className={({ selected }) =>
`px-4 py-2 border-b-2 ${selected ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500'}`
}>
Profile
</Tab>
<Tab className={({ selected }) =>
`px-4 py-2 border-b-2 ${selected ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500'}`
}>
Settings
</Tab>
</TabList>
<TabPanels>
<TabPanel className="p-4">
<p>Selected tab: {selectedIndex}</p>
<p>Profile content goes here...</p>
</TabPanel>
<TabPanel className="p-4">
<p>Settings content goes here...</p>
</TabPanel>
</TabPanels>
</TabGroup>
);
}
// Vertical tabs
function VerticalTabsExample() {
return (
<TabGroup vertical className="flex">
<TabList className="flex flex-col w-48 border-r border-gray-200">
<Tab className={({ selected }) =>
`px-4 py-3 text-left ${selected ? 'bg-blue-50 border-r-2 border-blue-500 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`
}>
General
</Tab>
<Tab className={({ selected }) =>
`px-4 py-3 text-left ${selected ? 'bg-blue-50 border-r-2 border-blue-500 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`
}>
Security
</Tab>
<Tab className={({ selected }) =>
`px-4 py-3 text-left ${selected ? 'bg-blue-50 border-r-2 border-blue-500 text-blue-700' : 'text-gray-600 hover:bg-gray-50'}`
}>
Notifications
</Tab>
</TabList>
<TabPanels className="flex-1 p-6">
<TabPanel>
<h2 className="text-xl font-semibold mb-4">General Settings</h2>
<p>General settings content...</p>
</TabPanel>
<TabPanel>
<h2 className="text-xl font-semibold mb-4">Security Settings</h2>
<p>Security settings content...</p>
</TabPanel>
<TabPanel>
<h2 className="text-xl font-semibold mb-4">Notification Settings</h2>
<p>Notification settings content...</p>
</TabPanel>
</TabPanels>
</TabGroup>
);
}A collapsible disclosure component for showing and hiding content.
/**
* Collapsible disclosure component
* @param props - Disclosure properties
*/
function Disclosure<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: DisclosureProps<TTag>
): JSX.Element;
interface DisclosureProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Default open state for uncontrolled usage */
defaultOpen?: boolean;
/** Render prop providing disclosure state */
children?: React.ReactNode | ((props: DisclosureRenderProps) => React.ReactNode);
}
interface DisclosureRenderProps {
/** Whether disclosure is open */
open: boolean;
/** Function to close disclosure */
close: (focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>) => void;
}Button to toggle the disclosure state.
/**
* Button to toggle the disclosure
* @param props - DisclosureButton properties
*/
function DisclosureButton<TTag extends keyof JSX.IntrinsicElements = 'button'>(
props: DisclosureButtonProps<TTag>
): JSX.Element;
interface DisclosureButtonProps<TTag extends keyof JSX.IntrinsicElements = 'button'>
extends PolymorphicProps<TTag> {
/** Whether button is disabled */
disabled?: boolean;
/** Whether to auto-focus on mount */
autoFocus?: boolean;
/** Render prop providing button state */
children?: React.ReactNode | ((props: DisclosureButtonRenderProps) => React.ReactNode);
}
interface DisclosureButtonRenderProps {
/** Whether disclosure is open */
open: boolean;
/** Whether being hovered */
hover: boolean;
/** Whether being pressed */
active: boolean;
/** Whether disabled */
disabled: boolean;
/** Whether has focus */
focus: boolean;
/** Whether has autofocus */
autofocus: boolean;
}The collapsible content panel.
/**
* Collapsible content panel
* @param props - DisclosurePanel properties
*/
function DisclosurePanel<TTag extends keyof JSX.IntrinsicElements = 'div'>(
props: DisclosurePanelProps<TTag>
): JSX.Element;
interface DisclosurePanelProps<TTag extends keyof JSX.IntrinsicElements = 'div'>
extends PolymorphicProps<TTag> {
/** Whether to use transition animations */
transition?: boolean;
/** Render prop providing panel state */
children?: React.ReactNode | ((props: DisclosurePanelRenderProps) => React.ReactNode);
}
interface DisclosurePanelRenderProps {
/** Whether disclosure is open */
open: boolean;
/** Function to close disclosure */
close: (focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>) => void;
}Usage Examples:
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react";
function BasicDisclosureExample() {
return (
<Disclosure>
<DisclosureButton className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500/75">
{({ open }) => (
<>
<span>What is your refund policy?</span>
<ChevronUpIcon
className={`${open ? 'rotate-180' : ''} h-5 w-5 text-purple-500 transition-transform`}
/>
</>
)}
</DisclosureButton>
<DisclosurePanel className="px-4 pb-2 pt-4 text-sm text-gray-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.
</DisclosurePanel>
</Disclosure>
);
}
// FAQ with multiple disclosures
function FAQExample() {
const faqs = [
{
question: "What's the best thing about Switzerland?",
answer: "I don't know, but the flag is a big plus. Lorem ipsum dolor sit amet consectetur adipisicing elit."
},
{
question: "How do you make holy water?",
answer: "You boil the hell out of it. Lorem ipsum dolor sit amet consectetur adipisicing elit."
},
{
question: "Why do you never see elephants hiding in trees?",
answer: "Because they're so good at it. Lorem ipsum dolor sit amet consectetur adipisicing elit."
}
];
return (
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2">
{faqs.map((faq, index) => (
<Disclosure key={index} as="div" className="mt-2">
<DisclosureButton className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500/75">
<span>{faq.question}</span>
<ChevronUpIcon className="h-5 w-5 text-purple-500 ui-open:rotate-180 ui-open:transform" />
</DisclosureButton>
<DisclosurePanel className="px-4 pb-2 pt-4 text-sm text-gray-500">
{faq.answer}
</DisclosurePanel>
</Disclosure>
))}
</div>
);
}
// Disclosure with transitions
function AnimatedDisclosureExample() {
return (
<Disclosure>
<DisclosureButton className="group flex w-full items-center justify-between rounded-lg bg-gray-100 px-4 py-2">
<span className="text-sm font-medium">Animated Disclosure</span>
<ChevronDownIcon className="w-5 h-5 group-data-[open]:rotate-180 transition-transform" />
</DisclosureButton>
<DisclosurePanel
transition
className="origin-top transition duration-200 ease-out data-[closed]:-translate-y-6 data-[closed]:opacity-0"
>
<div className="px-4 py-2 text-sm text-gray-600">
This content animates in and out smoothly when the disclosure is toggled.
</div>
</DisclosurePanel>
</Disclosure>
);
}// Floating UI anchor configuration for menu positioning
interface AnchorProps {
to?: string;
gap?: number;
offset?: number;
padding?: number;
}
// Common navigation render props
interface BaseNavigationRenderProps {
open: boolean;
close: () => void;
}
// Tab selection change handler
type TabChangeHandler = (index: number) => void;
// Close function with optional focus target
type CloseFunction = (
focusableElement?: HTMLElement | React.MutableRefObject<HTMLElement | null>
) => void;All navigation components include full keyboard support:
Components automatically include appropriate ARIA attributes: