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, Dialog, DialogTrigger, Menu, MenuItem, 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">
<DialogTrigger>
<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-none 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
placement="bottom end"
className="p-2 overflow-auto rounded-lg bg-white dark:bg-zinc-950 shadow-lg ring-1 ring-black dark:ring-white ring-opacity-10 dark:ring-opacity-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"
>
<Dialog className="outline-none">
<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-none">
<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>
</Dialog>
</Popover>
</DialogTrigger>
</div>
);
}
function MyMenuItem(props: MenuItemProps) {
return (
<MenuItem
{...props}
className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-none 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 transition duration-200 ease-in-out translate-x-0 group-selected:translate-x-[100%]" />
</div>
{children}
</>
))}
</Switch>
);
}
import {
Button,
composeRenderProps,
Dialog,
DialogTrigger,
Menu,
MenuItem,
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">
<DialogTrigger>
<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-none 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
placement="bottom end"
className="p-2 overflow-auto rounded-lg bg-white dark:bg-zinc-950 shadow-lg ring-1 ring-black dark:ring-white ring-opacity-10 dark:ring-opacity-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"
>
<Dialog className="outline-none">
<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-none">
<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>
</Dialog>
</Popover>
</DialogTrigger>
</div>
);
}
function MyMenuItem(props: MenuItemProps) {
return (
<MenuItem
{...props}
className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-none 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 transition duration-200 ease-in-out translate-x-0 group-selected:translate-x-[100%]" />
</div>
{children}
</>
))}
</Switch>
);
}
import {
Button,
composeRenderProps,
Dialog,
DialogTrigger,
Menu,
MenuItem,
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">
<DialogTrigger>
<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-none 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
placement="bottom end"
className="p-2 overflow-auto rounded-lg bg-white dark:bg-zinc-950 shadow-lg ring-1 ring-black dark:ring-white ring-opacity-10 dark:ring-opacity-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"
>
<Dialog className="outline-none">
<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-none">
<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>
</Dialog>
</Popover>
</DialogTrigger>
</div>
);
}
function MyMenuItem(
props: MenuItemProps
) {
return (
<MenuItem
{...props}
className="group flex w-full items-center rounded-md px-3 py-2 box-border outline-none 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 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:
Add them to your tailwind.config.js
:
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'
)
]
};
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.