Dropdown Menu

Menus offer an easy way to build custom, accessible dropdown components with robust support for keyboard navigation.

To get started, install Headless UI via npm:

npm install @headlessui/react

Menus are built using the Menu, MenuButton, MenuItems, and MenuItem components:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
      <MenuItems anchor="bottom">
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/settings">
            Settings
          </a>
        </MenuItem>
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/support">
            Support
          </a>
        </MenuItem>
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/license">
            License
          </a>
        </MenuItem>
      </MenuItems>
    </Menu>
  )
}

The MenuButton will automatically open and close the MenuItems when clicked, and when the menu is opened the list of items receives focus and is navigable via the keyboard.

Headless UI keeps track of a lot of state about each component, like which menu item is currently focused via the keyboard, whether a popover is open or closed, or which listbox option is currently selected.

But because the components are headless and completely unstyled out of the box, you can't see this information in your UI until you provide the styles you want for each state yourself.

The easiest way to style the different states of a Headless UI component is using the data-* attributes that each component exposes.

For example, the MenuButton component exposes a data-active attribute, which tells you if the menu is currently open, and the MenuItem component exposes a data-focus attribute, which tells you if the menu item is currently focused via the mouse or keyboard.

<!-- Rendered `MenuButton`, `MenuItems`, and `MenuItem` -->
<button data-active>Options</button>
<div data-open>
  <a href="/settings">Settings</a>
  <a href="/support" data-focus>Support</a>
  <a href="/license">License</a>
</div>

Use the CSS attribute selector to conditionally apply styles based on the presence of these data attributes. If you're using Tailwind CSS, the data attribute modifier makes this easy:

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

const links = [
  { href: '/settings', label: 'Settings' },
  { href: '/support', label: 'Support' },
  { href: '/license', label: 'License' },
]

function Example() {
  return (
    <Menu>
<MenuButton className="data-[active]:bg-blue-200">My account</MenuButton>
<MenuItems anchor="bottom"> {links.map((link) => (
<MenuItem key={link.href} className="block data-[focus]:bg-blue-100">
<a href={link.href}>{link.label}</a> </MenuItem> ))} </MenuItems> </Menu> ) }

See the component API for a list of all the available data attributes.

Each component also exposes information about its current state via render props that you can use to conditionally apply different styles or render different content.

For example, the MenuButton component exposes an active state, which tells you if the menu is currently open, and the MenuItem component exposes a focus state, which tells you if the menu item is currently focused via the mouse or keyboard.

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

const links = [
  { href: '/settings', label: 'Settings' },
  { href: '/support', label: 'Support' },
  { href: '/license', label: 'License' },
]

function Example() {
  return (
    <Menu>
<MenuButton as={Fragment}>
{({ active }) => <button className={clsx(active && 'bg-blue-200')}>My account</button>}
</MenuButton>
<MenuItems anchor="bottom"> {links.map((link) => (
<MenuItem key={link.href} as={Fragment}>
{({ focus }) => (
<a className={clsx('block', focus && 'bg-blue-100')} href={link.href}>
{link.label}
</a>
)}
</MenuItem>
))} </MenuItems> </Menu> ) }

See the component API for a list of all the available render props.

In addition to links, you can also use buttons in a MenuItem. This is useful when you want to trigger an action like opening a dialog or submitting a form.

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

function Example() {
  function showSettingsDialog() {
    alert('Open settings dialog!')
  }

  return (
    <Menu>
      <MenuButton>My account</MenuButton>
      <MenuItems anchor="bottom">
<MenuItem>
<button onClick={showSettingsDialog} className="block w-full text-left data-[focus]:bg-blue-100">
Settings
</button>
</MenuItem>
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem>
<form action="/logout" method="post">
<MenuItem>
<button type="submit" className="block w-full text-left data-[focus]:bg-blue-100">
Sign out
</button>
</MenuItem>
</form>
</MenuItems> </Menu> ) }

Use the disabled prop to disable a MenuItem and prevent it from being selected:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
      <MenuItems anchor="bottom">
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/settings">
            Settings
          </a>
        </MenuItem>
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/support">
            Support
          </a>
        </MenuItem>
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/license">
            License
          </a>
        </MenuItem>
<MenuItem disabled>
<a className="block data-[disabled]:opacity-50" href="/invite-a-friend">
Invite a friend (coming soon!)
</a>
</MenuItem>
</MenuItems> </Menu> ) }

Use the MenuSeparator component to add a visual separation between items in a menu.

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
      <MenuItems anchor="bottom">
        <MenuItem>
          <a className="block data-[focus]:bg-blue-100" href="/settings">
            Settings
          </a>
        </MenuItem>
<MenuSeparator className="my-1 h-px bg-black" />
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

Use the MenuSection, MenuHeading, and MenuSeparator components to group items into sections with labels:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
      <MenuItems anchor="bottom">
<MenuSection>
<MenuHeading className="text-sm opacity-50">Settings</MenuHeading>
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/profile"> My profile </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/notifications"> Notifications </a> </MenuItem>
</MenuSection>
<MenuSeparator className="my-1 h-px bg-black" />
<MenuSection>
<MenuHeading className="text-sm opacity-50">Support</MenuHeading>
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Documentation </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem>
</MenuSection>
</MenuItems> </Menu> ) }

The MenuItems dropdown has no width set by default, but you can add one using CSS:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
<MenuItems anchor="bottom" className="w-52">
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

If you'd like the dropdown width to match the MenuButton width, use the --button-width CSS variable that's exposed on the MenuItems element:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
<MenuItems anchor="bottom" className="w-[var(--button-width)]">
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

Add the anchor prop to the MenuItems to automatically position the dropdown relative to the MenuButton:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
<MenuItems anchor="bottom start">
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

Use the values top, right, bottom, or left to center the dropdown along the appropriate edge, or combine it with start or end to align the dropdown to a specific corner, such as top start or bottom end.

To control the gap between the button and the dropdown, use the --anchor-gap CSS variable:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
<MenuItems anchor="bottom start" className="[--anchor-gap:4px] sm:[--anchor-gap:8px]">
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

Additionally, you can use --anchor-offset to control the distance that the dropdown should be nudged from its original position, and --anchor-padding to control the minimum space that should exist between the dropdown and the viewport.

The anchor prop also supports an object API that allows you to control the gap, offset, and padding values using JavaScript:

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
<MenuItems anchor={{ to: 'bottom start', gap: '4px' }}>
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

See the MenuItems API for more information about these options.

To animate the opening/closing of the menu panel, use the provided Transition component. All you need to do is wrap the MenuItems in a Transition, and the transition will be applied automatically.

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

function Example() {
  return (
    <Menu>
      <MenuButton>My account</MenuButton>
<Transition
enter="duration-200 ease-out"
enterFrom="scale-95 opacity-0"
enterTo="scale-100 opacity-100"
leave="duration-300 ease-out"
leaveFrom="scale-100 opacity-100"
leaveTo="scale-95 opacity-0"
>
<MenuItems anchor="bottom" className="origin-top transition">
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems>
</Transition>
</Menu> ) }

By default our built-in Transition component automatically communicates with the Menu components to handle the open/closed states. However, Headless UI also composes well with other animation libraries in the React ecosystem like Framer Motion and React Spring. You just need to expose some state to those libraries.

For example, to animate the menu with Framer Motion, add the static prop to the MenuItems component and then conditionally render it based on the open render prop:

import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { AnimatePresence, motion } from 'framer-motion'

function Example() {
  return (
    <Menu>
{({ open }) => (
<> <MenuButton>My account</MenuButton> <AnimatePresence>
{open && (
<MenuItems
static
as={motion.div} initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} anchor="bottom" className="origin-top" >
<MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems>
)}
</AnimatePresence> </>
)}
</Menu> ) }

By default, the Menu will close when clicking a MenuItem. However, some third-party Link components use event.preventDefault() which prevents the menu from closing.

In these situations you can imperatively close the menu using the close render prop that's available on both the Menu and MenuItem components:

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

function Example() {
  return (
    <Menu>
      <MenuButton>Terms</MenuButton>
      <MenuItems anchor="bottom">
        <MenuItem>
{({ close }) => (
<MyCustomLink href="/" onClick={close}>
Read and accept </MyCustomLink>
)}
</MenuItem> </MenuItems> </Menu> ) }

By default, the Menu and its subcomponents each render a default element that is sensible for that component.

For example, MenuButton renders a button by default, and MenuItems renders a div. By contrast, Menu and MenuItem do not render an element, and instead render their children directly by default.

Use the as prop to render the component as a different element or as your own custom component, making sure your custom components forward refs so that Headless UI can wire things up correctly.

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

let MyCustomButton = forwardRef(function (props, ref) {
return <button className="..." ref={ref} {...props} />
})
function Example() { return ( <Menu>
<MenuButton as={MyCustomButton}>My account</MenuButton>
<MenuItems anchor="bottom" as="section">
<MenuItem as="a" className="block data-[focus]:bg-blue-100" href="/settings">
Settings </MenuItem>
<MenuItem as="a" className="block data-[focus]:bg-blue-100" href="/support">
Support </MenuItem>
<MenuItem as="a" className="block data-[focus]:bg-blue-100" href="/license">
License </MenuItem> </MenuItems> </Menu> ) }

To tell an element to render its children directly with no wrapper element, use as={Fragment}.

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

function Example() {
  return (
    <Menu>
<MenuButton as={Fragment}>
<button>My account</button>
</MenuButton>
<MenuItems anchor="bottom"> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/settings"> Settings </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/support"> Support </a> </MenuItem> <MenuItem> <a className="block data-[focus]:bg-blue-100" href="/license"> License </a> </MenuItem> </MenuItems> </Menu> ) }

This is important if you are using an interactive element like an <a> tag inside the MenuItem. If the MenuItem had an as="div", then the props provided by Headless UI would be forwarded to the div instead of the a, which means that you can't go to the URL provided by the <a> tag anymore via your keyboard.

Prior to Next.js v13, the Link component did not forward unknown props to the underlying a element, preventing the menu from closing on click when used inside a MenuItem.

If you're using Next.js v12 or older, you can work around this issue by creating your own component that wraps Link and forwards unknown props to the child a element:

import { forwardRef } from 'react'
import Link from 'next/link'
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/react'

const MyLink = forwardRef((props, ref) => {
let { href, children, ...rest } = props
return (
<Link href={href}>
<a ref={ref} {...rest}>
{children}
</a>
</Link>
)
})
function Example() { return ( <Menu> <MenuButton>My account</MenuButton> <MenuItems anchor="bottom"> <MenuItem>
<MyLink href="/settings">Settings</MyLink>
</MenuItem> </MenuItems> </Menu> ) }

This will ensure that all of the event listeners Headless UI needs to add to the a element are properly applied.

This behavior was changed in Next.js v13 making this workaround no longer necessary.

CommandDescription

Enter or Spacewhen MenuButton is focused

Opens menu and focuses first non-disabled item

ArrowDown or ArrowUpwhen MenuButton is focused

Opens menu and focuses first/last non-disabled item

Escwhen menu is open

Closes any open Menus

ArrowDown or ArrowUpwhen menu is open

Focuses previous/next non-disabled item

Home or PageUpwhen menu is open

Focuses first non-disabled item

End or PageDownwhen menu is open

Focuses last non-disabled item

Enter or Spacewhen menu is open

Activates/clicks the current menu item

A–Z or a–zwhen menu is open

Focuses first item that matches keyboard input

PropDefaultDescription
asFragment
String | Component

The element or component the menu should render as.

Data AttributeRender PropDescription
data-openopen

Boolean

Whether or not the menu is open.

close

() => void

Closes the menu and refocuses MenuButton.

PropDefaultDescription
asbutton
String | Component

The element or component the menu button should render as.

disabledfalse
Boolean

Whether or not the menu button is disabled.

Data AttributeRender PropDescription
data-openopen

Boolean

Whether or not the menu is open.

data-focusfocus

Boolean

Whether or not the menu button is focused.

data-hoverhover

Boolean

Whether or not the menu button is hovered.

data-activeactive

Boolean

Whether or not the menu button is in an active or pressed state.

data-autofocusautofocus

Boolean

Whether or not the autoFocus prop was set to true.

PropDefaultDescription
asdiv
String | Component

The element or component the menu items should render as.

anchor
Object

Configures the way the dropdown is anchored to the button.

anchor.tobottom
String

Where to position the menu items relative to the trigger.

Use the values top, right, bottom, left to center the menu items along the appropriate edge, or combine it with start or end to align the menu items to a specific corner, such as top start or bottom end.

anchor.gap0
Number | String

The space between the menu button and the menu items.

Can also be controlled using the --anchor-gap CSS variable.

anchor.offset0
Number | String

The distance the menu items should be nudged from its original position.

Can also be controlled using the --anchor-offset CSS variable.

anchor.padding0
Number | String

The minimum space between the menu items and the viewport.

Can also be controlled using the --anchor-padding CSS variable.

staticfalse
Boolean

Whether the element should ignore the internally managed open/closed state.

unmounttrue
Boolean

Whether the element should be unmounted or hidden based on the open/closed state.

portalfalse
Boolean

Whether the element should be rendered in a portal.

Automatically set to true when anchor prop is set.

modaltrue
Boolean

Whether to enable accessibility features like scroll locking, focus trapping, and making other elements inert.

Data AttributeRender PropDescription
data-openopen

Boolean

Whether or not the menu is open.

PropDefaultDescription
asFragment
String | Component

The element or component the menu item should render as.

disabledfalse
Boolean

Whether or not the menu item is disabled for keyboard navigation and ARIA purposes.

Data AttributeRender PropDescription
data-disableddisabled

Boolean

Whether or not the menu item is disabled.

data-focusfocus

Boolean

Whether or not the menu item is focused.

close

() => void

Closes the menu and refocuses MenuButton.

Divides a list of MenuItem components into sections with proper accessibility semantics.

PropDefaultDescription
asdiv
String | Component

The element or component the menu section should render as.

Adds an accessible label to a MenuSection.

PropDefaultDescription
asheader
String | Component

The element or component the menu heading should render as.

Separates two MenuSection components, with proper accessibility semantics.

PropDefaultDescription
asdiv
String | Component

The element or component the menu separator should render as.

If you're interested in predesigned component examples using Headless UI and Tailwind CSS, check out Tailwind UI — a collection of beautifully designed and expertly crafted components built by us.

It's a great way to support our work on open-source projects like this and makes it possible for us to improve them and keep them well-maintained.