React AriaExamples

Account Menu

A Menu with an interactive header, built with a Dialog and Popover.

Example#


For accessibility reasons, standalone Menus cannot contain any children other than menu items (optionally grouped into sections). To build patterns that include such elements, place the Menu into a Dialog with additional interactive elements as siblings.

import {Button, composeRenderProps, Menu, MenuItem, MenuTrigger, Popover, Separator, Switch} from 'react-aria-components';
import type {MenuItemProps, SwitchProps} from 'react-aria-components';

function AccountMenuExample() {
  return (
    <div className="p-8 bg-gray-50 dark:bg-zinc-900 rounded-lg flex items-start justify-center">
      <MenuTrigger>
        <Button
          aria-label="Account"
          className="inline-flex items-center justify-center rounded-md p-1.5 text-white bg-transparent border-none hover:bg-gray-200 pressed:bg-gray-300 dark:hover:bg-zinc-800 dark:pressed:bg-zinc-700 transition-colors cursor-default outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600"
        >
          <img
            alt=""
            src="https://i.imgur.com/xIe7Wlb.png"
            className="w-7 h-7 rounded-full"
          />
        </Button>
        <Popover className="p-2 overflow-auto outline-hidden rounded-lg bg-white dark:bg-zinc-950 shadow-lg ring-1 ring-black/10 dark:ring-white/15 entering:animate-in entering:fade-in entering:placement-bottom:slide-in-from-top-1 entering:placement-top:slide-in-from-bottom-1 exiting:animate-out exiting:fade-out exiting:placement-bottom:slide-out-to-top-1 exiting:placement-top:slide-out-to-bottom-1 fill-mode-forwards origin-top-left">
          <div className="flex gap-2 items-center mx-3 mt-2">
            <img
              alt=""
              src="https://i.imgur.com/xIe7Wlb.png"
              className="w-16 h-16 rounded-full"
            />
            <div className="flex flex-col gap-1">
              <div className="text-[15px] font-bold text-gray-900 dark:text-gray-100 leading-none">
                Marissa Whitaker
              </div>
              <div className="text-base text-gray-900 dark:text-gray-100 leading-none mb-1">
                user@example.com
              </div>
              <MySwitch>Dark Mode</MySwitch>
            </div>
          </div>
          <Separator className="border-none bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 mt-4 mb-2" />
          <Menu className="outline-hidden">
            <MyMenuItem id="new">Account Settings</MyMenuItem>
            <MyMenuItem id="open">Support</MyMenuItem>
            <Separator className="bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 my-2" />
            <MyMenuItem id="save">Legal notices</MyMenuItem>
            <MyMenuItem id="save-as">About</MyMenuItem>
            <Separator className="bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 my-2" />
            <MyMenuItem id="print">Sign out</MyMenuItem>
          </Menu>
        </Popover>
      </MenuTrigger>
    </div>
  );
}

function MyMenuItem(props: MenuItemProps) {
  return (
    <MenuItem
      {...props}
      className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-hidden cursor-default text-gray-900 dark:text-gray-100 focus:bg-blue-500 focus:text-white"
    />
  );
}

function MySwitch(props: SwitchProps) {
  return (
    <Switch className="group flex gap-2 items-center text-gray-800 dark:text-zinc-200 text-base transition">
      {composeRenderProps(props.children, (children) => (
        <>
          <div className="flex h-3 w-6 p-[2px] items-center shrink-0 cursor-default rounded-full transition duration-200 ease-in-out shadow-inner border border-transparent bg-gray-400 dark:bg-zinc-400 group-pressed:bg-gray-500 dark:group-pressed:bg-zinc-300 group-selected:bg-gray-700 group-selected:dark:bg-zinc-300 group-selected:forced-colors:bg-[Highlight]! group-selected:group-pressed:bg-gray-800 group-selected:dark:group-pressed:bg-zinc-200 outline outline-0 outline-blue-600 dark:outline-blue-500 forced-colors:outline-[Highlight] outline-offset-2 group-focus-visible:outline-2">
            <div className="h-3 w-3 transform rounded-full bg-white dark:bg-zinc-900 outline outline-1 -outline-offset-1 outline-transparent shadow-sm transition duration-200 ease-in-out translate-x-0 group-selected:translate-x-[100%]" />
          </div>
          {children}
        </>
      ))}
    </Switch>
  );
}
import {
  Button,
  composeRenderProps,
  Menu,
  MenuItem,
  MenuTrigger,
  Popover,
  Separator,
  Switch
} from 'react-aria-components';
import type {
  MenuItemProps,
  SwitchProps
} from 'react-aria-components';

function AccountMenuExample() {
  return (
    <div className="p-8 bg-gray-50 dark:bg-zinc-900 rounded-lg flex items-start justify-center">
      <MenuTrigger>
        <Button
          aria-label="Account"
          className="inline-flex items-center justify-center rounded-md p-1.5 text-white bg-transparent border-none hover:bg-gray-200 pressed:bg-gray-300 dark:hover:bg-zinc-800 dark:pressed:bg-zinc-700 transition-colors cursor-default outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600"
        >
          <img
            alt=""
            src="https://i.imgur.com/xIe7Wlb.png"
            className="w-7 h-7 rounded-full"
          />
        </Button>
        <Popover className="p-2 overflow-auto outline-hidden rounded-lg bg-white dark:bg-zinc-950 shadow-lg ring-1 ring-black/10 dark:ring-white/15 entering:animate-in entering:fade-in entering:placement-bottom:slide-in-from-top-1 entering:placement-top:slide-in-from-bottom-1 exiting:animate-out exiting:fade-out exiting:placement-bottom:slide-out-to-top-1 exiting:placement-top:slide-out-to-bottom-1 fill-mode-forwards origin-top-left">
          <div className="flex gap-2 items-center mx-3 mt-2">
            <img
              alt=""
              src="https://i.imgur.com/xIe7Wlb.png"
              className="w-16 h-16 rounded-full"
            />
            <div className="flex flex-col gap-1">
              <div className="text-[15px] font-bold text-gray-900 dark:text-gray-100 leading-none">
                Marissa Whitaker
              </div>
              <div className="text-base text-gray-900 dark:text-gray-100 leading-none mb-1">
                user@example.com
              </div>
              <MySwitch>Dark Mode</MySwitch>
            </div>
          </div>
          <Separator className="border-none bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 mt-4 mb-2" />
          <Menu className="outline-hidden">
            <MyMenuItem id="new">
              Account Settings
            </MyMenuItem>
            <MyMenuItem id="open">Support</MyMenuItem>
            <Separator className="bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 my-2" />
            <MyMenuItem id="save">Legal notices</MyMenuItem>
            <MyMenuItem id="save-as">About</MyMenuItem>
            <Separator className="bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 my-2" />
            <MyMenuItem id="print">Sign out</MyMenuItem>
          </Menu>
        </Popover>
      </MenuTrigger>
    </div>
  );
}

function MyMenuItem(props: MenuItemProps) {
  return (
    <MenuItem
      {...props}
      className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-hidden cursor-default text-gray-900 dark:text-gray-100 focus:bg-blue-500 focus:text-white"
    />
  );
}

function MySwitch(props: SwitchProps) {
  return (
    <Switch className="group flex gap-2 items-center text-gray-800 dark:text-zinc-200 text-base transition">
      {composeRenderProps(props.children, (children) => (
        <>
          <div className="flex h-3 w-6 p-[2px] items-center shrink-0 cursor-default rounded-full transition duration-200 ease-in-out shadow-inner border border-transparent bg-gray-400 dark:bg-zinc-400 group-pressed:bg-gray-500 dark:group-pressed:bg-zinc-300 group-selected:bg-gray-700 group-selected:dark:bg-zinc-300 group-selected:forced-colors:bg-[Highlight]! group-selected:group-pressed:bg-gray-800 group-selected:dark:group-pressed:bg-zinc-200 outline outline-0 outline-blue-600 dark:outline-blue-500 forced-colors:outline-[Highlight] outline-offset-2 group-focus-visible:outline-2">
            <div className="h-3 w-3 transform rounded-full bg-white dark:bg-zinc-900 outline outline-1 -outline-offset-1 outline-transparent shadow-sm transition duration-200 ease-in-out translate-x-0 group-selected:translate-x-[100%]" />
          </div>
          {children}
        </>
      ))}
    </Switch>
  );
}
import {
  Button,
  composeRenderProps,
  Menu,
  MenuItem,
  MenuTrigger,
  Popover,
  Separator,
  Switch
} from 'react-aria-components';
import type {
  MenuItemProps,
  SwitchProps
} from 'react-aria-components';

function AccountMenuExample() {
  return (
    <div className="p-8 bg-gray-50 dark:bg-zinc-900 rounded-lg flex items-start justify-center">
      <MenuTrigger>
        <Button
          aria-label="Account"
          className="inline-flex items-center justify-center rounded-md p-1.5 text-white bg-transparent border-none hover:bg-gray-200 pressed:bg-gray-300 dark:hover:bg-zinc-800 dark:pressed:bg-zinc-700 transition-colors cursor-default outline-hidden focus-visible:ring-2 focus-visible:ring-blue-600"
        >
          <img
            alt=""
            src="https://i.imgur.com/xIe7Wlb.png"
            className="w-7 h-7 rounded-full"
          />
        </Button>
        <Popover className="p-2 overflow-auto outline-hidden rounded-lg bg-white dark:bg-zinc-950 shadow-lg ring-1 ring-black/10 dark:ring-white/15 entering:animate-in entering:fade-in entering:placement-bottom:slide-in-from-top-1 entering:placement-top:slide-in-from-bottom-1 exiting:animate-out exiting:fade-out exiting:placement-bottom:slide-out-to-top-1 exiting:placement-top:slide-out-to-bottom-1 fill-mode-forwards origin-top-left">
          <div className="flex gap-2 items-center mx-3 mt-2">
            <img
              alt=""
              src="https://i.imgur.com/xIe7Wlb.png"
              className="w-16 h-16 rounded-full"
            />
            <div className="flex flex-col gap-1">
              <div className="text-[15px] font-bold text-gray-900 dark:text-gray-100 leading-none">
                Marissa
                Whitaker
              </div>
              <div className="text-base text-gray-900 dark:text-gray-100 leading-none mb-1">
                user@example.com
              </div>
              <MySwitch>
                Dark Mode
              </MySwitch>
            </div>
          </div>
          <Separator className="border-none bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 mt-4 mb-2" />
          <Menu className="outline-hidden">
            <MyMenuItem id="new">
              Account
              Settings
            </MyMenuItem>
            <MyMenuItem id="open">
              Support
            </MyMenuItem>
            <Separator className="bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 my-2" />
            <MyMenuItem id="save">
              Legal
              notices
            </MyMenuItem>
            <MyMenuItem id="save-as">
              About
            </MyMenuItem>
            <Separator className="bg-gray-300 dark:bg-zinc-600 h-[1px] mx-3 my-2" />
            <MyMenuItem id="print">
              Sign out
            </MyMenuItem>
          </Menu>
        </Popover>
      </MenuTrigger>
    </div>
  );
}

function MyMenuItem(
  props: MenuItemProps
) {
  return (
    <MenuItem
      {...props}
      className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-hidden cursor-default text-gray-900 dark:text-gray-100 focus:bg-blue-500 focus:text-white"
    />
  );
}

function MySwitch(
  props: SwitchProps
) {
  return (
    <Switch className="group flex gap-2 items-center text-gray-800 dark:text-zinc-200 text-base transition">
      {composeRenderProps(
        props.children,
        (children) => (
          <>
            <div className="flex h-3 w-6 p-[2px] items-center shrink-0 cursor-default rounded-full transition duration-200 ease-in-out shadow-inner border border-transparent bg-gray-400 dark:bg-zinc-400 group-pressed:bg-gray-500 dark:group-pressed:bg-zinc-300 group-selected:bg-gray-700 group-selected:dark:bg-zinc-300 group-selected:forced-colors:bg-[Highlight]! group-selected:group-pressed:bg-gray-800 group-selected:dark:group-pressed:bg-zinc-200 outline outline-0 outline-blue-600 dark:outline-blue-500 forced-colors:outline-[Highlight] outline-offset-2 group-focus-visible:outline-2">
              <div className="h-3 w-3 transform rounded-full bg-white dark:bg-zinc-900 outline outline-1 -outline-offset-1 outline-transparent shadow-sm transition duration-200 ease-in-out translate-x-0 group-selected:translate-x-[100%]" />
            </div>
            {children}
          </>
        )
      )}
    </Switch>
  );
}

Tailwind config#

This example uses the following plugins:

When using Tailwind v4, add them to your CSS:

@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
@plugin "tailwindcss-animate";
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
@plugin "tailwindcss-animate";
@import "tailwindcss";
@plugin "tailwindcss-react-aria-components";
@plugin "tailwindcss-animate";
Tailwind v3

When using Tailwind v3, add the plugins to your tailwind.config.js instead:

module.exports = {
  // ...
  plugins: [
    require('tailwindcss-react-aria-components'),
    require('tailwindcss-animate')
  ]
};
module.exports = {
  // ...
  plugins: [
    require('tailwindcss-react-aria-components'),
    require('tailwindcss-animate')
  ]
};
module.exports = {
  // ...
  plugins: [
    require(
      'tailwindcss-react-aria-components'
    ),
    require(
      'tailwindcss-animate'
    )
  ]
};

Note: When using Tailwind v3, install tailwindcss-react-aria-components version 1.x instead of 2.x.

Components#


Menu
A menu displays a list of actions or options that a user can choose.
Button
A button allows a user to perform an action.
Popover
A popover displays content in context with a trigger element.
Dialog
A dialog is an overlay shown above other content in an application.