Menu
A list of actions in a dropdown, enhanced with keyboard navigation.
"use client";
import { useState } from "react";
import {
IconBell,
IconCreditCard,
IconDeviceDesktop,
IconDeviceFloppy,
IconDots,
IconDownload,
IconEye,
IconFile,
IconFileCode,
IconFileText,
IconFolder,
IconFolderOpen,
IconFolderSearch,
IconHelpCircle,
IconKeyboard,
IconLanguage,
IconLayout,
IconLogout,
IconMail,
IconMoon,
IconPalette,
IconSettings,
IconShield,
IconSun,
IconUser,
} from "@tabler/icons-react";
import { Button } from "@/components/ui/button";
import {
Menu,
MenuCheckboxItem,
MenuGroup,
MenuGroupLabel,
MenuItem,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuSeparator,
MenuShortcut,
MenuSub,
MenuSubPopup,
MenuSubTrigger,
MenuTrigger,
} from "@/components/ui/menu";
export function MenuDemo() {
const [notifications, setNotifications] = useState({
email: true,
sms: false,
push: true,
});
const [theme, setTheme] = useState("light");
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuPopup>
<MenuGroup>
<MenuGroupLabel>File</MenuGroupLabel>
<MenuItem>
<IconFile />
New File
<MenuShortcut>⌘N</MenuShortcut>
</MenuItem>
<MenuItem>
<IconFolder />
New Folder
<MenuShortcut>⇧⌘N</MenuShortcut>
</MenuItem>
<MenuSub>
<MenuSubTrigger>
<IconFolderOpen />
Open Recent
</MenuSubTrigger>
<MenuSubPopup>
<MenuGroup>
<MenuGroupLabel>Recent Projects</MenuGroupLabel>
<MenuItem>
<IconFileCode />
Project Alpha
</MenuItem>
<MenuItem>
<IconFileCode />
Project Beta
</MenuItem>
<MenuSub>
<MenuSubTrigger>
<IconDots />
More Projects
</MenuSubTrigger>
<MenuSubPopup>
<MenuItem>
<IconFileCode />
Project Gamma
</MenuItem>
<MenuItem>
<IconFileCode />
Project Delta
</MenuItem>
</MenuSubPopup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem>
<IconFolderSearch />
Browse...
</MenuItem>
</MenuGroup>
</MenuSubPopup>
</MenuSub>
<MenuSeparator />
<MenuItem>
<IconDeviceFloppy />
Save
<MenuShortcut>⌘S</MenuShortcut>
</MenuItem>
<MenuItem>
<IconDownload />
Export
<MenuShortcut>⇧⌘E</MenuShortcut>
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuGroupLabel>View</MenuGroupLabel>
<MenuCheckboxItem>
<IconLayout />
Show Sidebar
</MenuCheckboxItem>
<MenuCheckboxItem>
<IconEye />
Show Status Bar
</MenuCheckboxItem>
<MenuSub>
<MenuSubTrigger>
<IconPalette />
Theme
</MenuSubTrigger>
<MenuSubPopup>
<MenuGroup>
<MenuGroupLabel>Appearance</MenuGroupLabel>
<MenuRadioGroup value={theme} onValueChange={setTheme}>
<MenuRadioItem value="light">
<IconSun />
Light
</MenuRadioItem>
<MenuRadioItem value="dark">
<IconMoon />
Dark
</MenuRadioItem>
<MenuRadioItem value="system">
<IconDeviceDesktop />
System
</MenuRadioItem>
</MenuRadioGroup>
</MenuGroup>
</MenuSubPopup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuGroupLabel>Account</MenuGroupLabel>
<MenuItem>
<IconUser />
Profile
<MenuShortcut>⇧⌘P</MenuShortcut>
</MenuItem>
<MenuItem disabled>
<IconCreditCard />
Billing
</MenuItem>
<MenuSub>
<MenuSubTrigger>
<IconSettings />
Settings
</MenuSubTrigger>
<MenuSubPopup>
<MenuGroup>
<MenuGroupLabel>Preferences</MenuGroupLabel>
<MenuItem>
<IconKeyboard />
Keyboard Shortcuts
</MenuItem>
<MenuItem>
<IconLanguage />
Language
</MenuItem>
<MenuSub>
<MenuSubTrigger>
<IconBell />
Notifications
</MenuSubTrigger>
<MenuSubPopup>
<MenuGroup>
<MenuGroupLabel>Notification Types</MenuGroupLabel>
<MenuCheckboxItem
variant="switch"
checked={notifications.push}
onCheckedChange={(checked) =>
setNotifications({
...notifications,
push: checked === true,
})
}
>
<IconBell />
Push Notifications
</MenuCheckboxItem>
<MenuCheckboxItem
variant="switch"
checked={notifications.email}
onCheckedChange={(checked) =>
setNotifications({
...notifications,
email: checked === true,
})
}
>
<IconMail />
Email Notifications
</MenuCheckboxItem>
</MenuGroup>
</MenuSubPopup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem>
<IconShield />
Privacy & Security
</MenuItem>
</MenuGroup>
</MenuSubPopup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem>
<IconHelpCircle />
Help & Support
</MenuItem>
<MenuItem>
<IconFileText />
Documentation
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem variant="danger">
<IconLogout />
Sign Out
<MenuShortcut>⇧⌘Q</MenuShortcut>
</MenuItem>
</MenuGroup>
</MenuPopup>
</Menu>
);
}Installation
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
import { IconChevronRight } from "@tabler/icons-react";
import { cn } from "@/lib/cn";
import { ScrollArea } from "@/components/ui/scroll-area";
const menuCreateHandle = MenuPrimitive.createHandle;
function Menu({ ...props }: React.ComponentProps<typeof MenuPrimitive.Root>) {
return <MenuPrimitive.Root data-slot="menu" {...props} />;
}
function MenuPortal(props: React.ComponentProps<typeof MenuPrimitive.Portal>) {
return <MenuPrimitive.Portal data-slot="menu-portal" {...props} />;
}
function MenuTrigger(props: React.ComponentProps<typeof MenuPrimitive.Trigger>) {
return <MenuPrimitive.Trigger data-slot="menu-trigger" {...props} />;
}
function MenuPositioner({
className,
align = "center",
side = "bottom",
sideOffset = 4,
...props
}: React.ComponentProps<typeof MenuPrimitive.Positioner>) {
return (
<MenuPrimitive.Positioner
data-slot="menu-positioner"
align={align}
side={side}
sideOffset={sideOffset}
className={cn("z-50 flex", className)}
{...props}
/>
);
}
function MenuPopup({
children,
className,
scrollArea = false,
positionerProps,
...props
}: React.ComponentProps<typeof MenuPrimitive.Popup> & {
scrollArea?: boolean;
positionerProps?: React.ComponentProps<typeof MenuPositioner>;
}) {
return (
<MenuPrimitive.Portal>
<MenuPositioner {...positionerProps}>
<MenuPrimitive.Popup
data-slot="menu-popup"
className={cn(
"flex not-[class*='w-']:min-w-(--anchor-width) origin-(--transform-origin) overflow-hidden rounded-lg border bg-popover not-dark:bg-clip-padding shadow-lg outline-none transition-[transform,scale,opacity] focus:outline-none data-ending-style:scale-90 data-starting-style:scale-90 data-ending-style:opacity-0 data-starting-style:opacity-0",
className
)}
{...props}
>
{scrollArea ? (
<ScrollArea scrollbarGutter scrollFade className="max-h-(--available-height) p-1">
{children}
</ScrollArea>
) : (
<div className="max-h-(--available-height) w-full overflow-y-auto p-1 [scrollbar-width:thin]">
{children}
</div>
)}
</MenuPrimitive.Popup>
</MenuPositioner>
</MenuPrimitive.Portal>
);
}
function MenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof MenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "danger";
}) {
return (
<MenuPrimitive.Item
data-slot="menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"flex min-h-7 cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-sm outline-none data-highlighted:data-[variant=danger]:bg-danger/10 data-disabled:pointer-events-none data-highlighted:bg-accent data-inset:ps-8 data-[variant=danger]:text-danger data-highlighted:text-accent-foreground data-disabled:opacity-50 [&>svg:not([class*='opacity-'])]:opacity-80 [&>svg:not([class*='size-'])]:size-4 [&>svg]:pointer-events-none [&>svg]:shrink-0",
className
)}
{...props}
/>
);
}
function MenuCheckboxItem({
className,
children,
checked,
variant = "default",
indicatorPosition = "end",
...props
}: React.ComponentProps<typeof MenuPrimitive.CheckboxItem> & {
variant?: "default" | "switch";
indicatorPosition?: "start" | "end";
}) {
return (
<MenuPrimitive.CheckboxItem
data-slot="menu-checkbox-item"
checked={checked}
className={cn(
"grid min-h-7 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm px-2 py-1 text-sm outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variant === "switch"
? "grid-cols-[1fr_auto] [--switch-thumb:--spacing(3)]"
: indicatorPosition === "start"
? "grid-cols-[1rem_1fr] pe-4"
: "grid-cols-[1fr_1rem]",
variant === "switch" && indicatorPosition === "start" && "grid-cols-[auto_1fr] pe-4",
className
)}
{...props}
>
{variant === "switch" ? (
<>
<span
className={cn(
"flex items-center gap-2",
indicatorPosition === "start" && "order-1 col-start-2 gap-1.5"
)}
>
{children}
</span>
<MenuPrimitive.CheckboxItemIndicator
data-slot="menu-checkbox-item-indicator"
className={cn(
"inset-shadow-[0_1px_--theme(--color-black/6%)] inline-flex h-[calc(var(--switch-thumb)+2px)] w-[calc(var(--switch-thumb)*2-2px)] shrink-0 items-center rounded-full p-px outline-none transition-[background-color,box-shadow] duration-200 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50"
)}
keepMounted
>
<span className="pointer-events-none block aspect-square h-full in-[[data-slot=menu-checkbox-item][data-checked]]:origin-[var(--switch-thumb)_50%] origin-left in-[[data-slot=menu-checkbox-item][data-checked]]:translate-x-[calc(var(--switch-thumb)-4px)] in-[[data-slot=menu-checkbox-item]:active]:not-data-disabled:scale-x-110 in-[[data-slot=menu-checkbox-item]:active]:rounded-[var(--switch-thumb)/calc(var(--switch-thumb)*1.10)] rounded-full bg-background shadow-sm/5 will-change-transform [transition:translate_.15s,border-radius_.15s,scale_.1s_.1s,transform-origin_.15s]" />
</MenuPrimitive.CheckboxItemIndicator>
</>
) : (
<>
<span
className={cn(
"flex items-center gap-2",
indicatorPosition === "start" && "order-1 col-start-2 gap-1.5"
)}
>
{children}
</span>
<MenuPrimitive.CheckboxItemIndicator data-slot="menu-checkbox-item-indicator">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</MenuPrimitive.CheckboxItemIndicator>
</>
)}
</MenuPrimitive.CheckboxItem>
);
}
function MenuRadioGroup(props: React.ComponentProps<typeof MenuPrimitive.RadioGroup>) {
return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
}
function MenuRadioItem({
className,
children,
indicatorPosition = "end",
...props
}: React.ComponentProps<typeof MenuPrimitive.RadioItem> & {
indicatorPosition?: "start" | "end";
}) {
return (
<MenuPrimitive.RadioItem
data-slot="menu-radio-item"
className={cn(
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm px-2 py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-50 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
indicatorPosition === "start" ? "grid-cols-[1rem_1fr] pe-4" : "grid-cols-[1fr_1rem]",
className
)}
{...props}
>
<span
className={cn(
"flex items-center gap-1.5",
indicatorPosition === "start" && "order-1 col-start-2"
)}
>
{children}
</span>
<MenuPrimitive.RadioItemIndicator data-slot="menu-radio-item-indicator">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</MenuPrimitive.RadioItemIndicator>
</MenuPrimitive.RadioItem>
);
}
function MenuGroup(props: React.ComponentProps<typeof MenuPrimitive.Group>) {
return <MenuPrimitive.Group data-slot="menu-group" {...props} />;
}
function MenuGroupLabel({
className,
inset,
...props
}: React.ComponentProps<typeof MenuPrimitive.GroupLabel> & {
inset?: boolean;
}) {
return (
<MenuPrimitive.GroupLabel
data-slot="menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 font-medium text-muted-foreground text-xs data-inset:ps-9 sm:data-inset:ps-8",
className
)}
{...props}
/>
);
}
function MenuSeparator({
className,
...props
}: React.ComponentProps<typeof MenuPrimitive.Separator>) {
return (
<MenuPrimitive.Separator
className={cn("mx-2 my-1 h-px bg-border", className)}
data-slot="menu-separator"
{...props}
/>
);
}
function MenuShortcut({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="menu-shortcut"
className={cn(
"ms-auto font-medium font-sans text-muted-foreground/70 text-xs tracking-widest",
className
)}
{...props}
/>
);
}
function MenuSub(props: React.ComponentProps<typeof MenuPrimitive.SubmenuRoot>) {
return <MenuPrimitive.SubmenuRoot data-slot="menu-sub" {...props} />;
}
function MenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof MenuPrimitive.SubmenuTrigger> & {
inset?: boolean;
}) {
return (
<MenuPrimitive.SubmenuTrigger
data-slot="menu-sub-trigger"
data-inset={inset}
className={cn(
"flex min-h-7 items-center gap-2 rounded-sm px-2 py-1 text-sm outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-popup-open:bg-accent data-inset:ps-8 data-highlighted:text-accent-foreground data-popup-open:text-accent-foreground data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
className
)}
{...props}
>
{children}
<IconChevronRight className="ms-auto -me-0.5 opacity-80" />
</MenuPrimitive.SubmenuTrigger>
);
}
function MenuSubPopup({
className,
positionerProps: {
align = "start",
side = "inline-end",
alignOffset,
sideOffset = 1,
...restPositionProps
} = {},
...props
}: React.ComponentProps<typeof MenuPopup>) {
return (
<MenuPopup
data-slot="menu-sub-popup"
positionerProps={{
align,
alignOffset: alignOffset ?? (align !== "center" ? -5 : undefined),
side,
sideOffset,
...restPositionProps,
}}
{...props}
/>
);
}
export {
menuCreateHandle,
menuCreateHandle as DropdownMenuCreateHandle,
Menu,
Menu as DropdownMenu,
MenuPortal,
MenuPortal as DropdownMenuPortal,
MenuTrigger,
MenuTrigger as DropdownMenuTrigger,
MenuPositioner,
MenuPositioner as DropdownMenuPositioner,
MenuPopup,
MenuPopup as DropdownMenuContent,
MenuGroup,
MenuGroup as DropdownMenuGroup,
MenuItem,
MenuItem as DropdownMenuItem,
MenuCheckboxItem,
MenuCheckboxItem as DropdownMenuCheckboxItem,
MenuRadioGroup,
MenuRadioGroup as DropdownMenuRadioGroup,
MenuRadioItem,
MenuRadioItem as DropdownMenuRadioItem,
MenuGroupLabel,
MenuGroupLabel as DropdownMenuLabel,
MenuSeparator,
MenuSeparator as DropdownMenuSeparator,
MenuShortcut,
MenuShortcut as DropdownMenuShortcut,
MenuSub,
MenuSub as DropdownMenuSub,
MenuSubTrigger,
MenuSubTrigger as DropdownMenuSubTrigger,
MenuSubPopup,
MenuSubPopup as DropdownMenuSubContent,
};Usage
import {
Menu,
MenuCheckboxItem,
MenuGroup,
MenuGroupLabel,
MenuItem,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuSeparator,
MenuShortcut,
MenuSub,
MenuSubPopup,
MenuSubTrigger,
MenuTrigger,
} from "@/registry/default/ui/menu";<Menu>
<MenuTrigger>Menu</MenuTrigger>
<MenuPopup>
<MenuGroup>
<MenuGroupLabel>File</MenuGroupLabel>
<MenuItem>
New File
<MenuShortcut>⌘N</MenuShortcut>
</MenuItem>
<MenuItem>
New Folder
<MenuShortcut>⇧⌘N</MenuShortcut>
</MenuItem>
<MenuSub>
<MenuSubTrigger>Open Recent</MenuSubTrigger>
<MenuSubPopup>
<MenuGroup>
<MenuGroupLabel>Recent Projects</MenuGroupLabel>
<MenuItem>Project Alpha</MenuItem>
<MenuItem>Project Beta</MenuItem>
</MenuGroup>
</MenuSubPopup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuGroupLabel>View</MenuGroupLabel>
<MenuSub>
<MenuSubTrigger>Theme</MenuSubTrigger>
<MenuSubPopup>
<MenuGroup>
<MenuGroupLabel>Appearance</MenuGroupLabel>
<MenuRadioGroup>
<MenuRadioItem value="light">Light</MenuRadioItem>
<MenuRadioItem value="dark">Dark</MenuRadioItem>
<MenuRadioItem value="system">System</MenuRadioItem>
</MenuRadioGroup>
</MenuGroup>
</MenuSubPopup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuGroupLabel>Account</MenuGroupLabel>
<MenuItem>Profile</MenuItem>
<MenuItem>Billing</MenuItem>
<MenuSub>
<MenuSubTrigger>Settings</MenuSubTrigger>
<MenuSeparator />
<MenuGroup>
<MenuGroupLabel>Notifications</MenuGroupLabel>
<MenuCheckboxItem variant="switch">Push Notifications</MenuCheckboxItem>
<MenuCheckboxItem variant="switch">Email Notifications</MenuCheckboxItem>
</MenuGroup>
</MenuSub>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem variant="danger">
Sign Out
<MenuShortcut>⇧⌘Q</MenuShortcut>
</MenuItem>
</MenuGroup>
</MenuPopup>
</Menu>Examples
Open on hover
Enable opening the menu when hovering over the trigger by setting the openOnHover prop on MenuTrigger.
import { Button } from "@/components/ui/button";
import { Menu, MenuItem, MenuPopup, MenuTrigger } from "@/components/ui/menu";
export function MenuDemo() {
return (
<Menu>
<MenuTrigger openOnHover render={<Button variant="outline">Hover</Button>} />
<MenuPopup>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
);
}With Checkbox
import { Button } from "@/components/ui/button";
import { Menu, MenuCheckboxItem, MenuPopup, MenuTrigger } from "@/components/ui/menu";
export function MenuDemo() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Settings</Button>} />
<MenuPopup>
<MenuCheckboxItem>Sidebar</MenuCheckboxItem>
<MenuCheckboxItem>Status bar</MenuCheckboxItem>
<MenuCheckboxItem>Activity bar</MenuCheckboxItem>
</MenuPopup>
</Menu>
);
}With Radio group
import { Button } from "@/components/ui/button";
import {
Menu,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuTrigger,
} from "@/components/ui/menu";
export function MenuDemo() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline" />}>Theme</MenuTrigger>
<MenuPopup>
<MenuRadioGroup>
<MenuRadioItem value="light">Light</MenuRadioItem>
<MenuRadioItem value="dark">Dark</MenuRadioItem>
<MenuRadioItem value="system">System</MenuRadioItem>
</MenuRadioGroup>
</MenuPopup>
</Menu>
);
}Switch variant & indicator position
Menu checkbox items support a switch variant and an indicatorPosition prop to position the indicator at the start or end of the item. Default indicatorPosition is end.
Menu radio items support an indicatorPosition prop to position the indicator at the start or end of the item.
Default indicatorPosition is end.
import { Button } from "@/components/ui/button";
import {
Menu,
MenuCheckboxItem,
MenuGroup,
MenuGroupLabel,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuTrigger,
} from "@/components/ui/menu";
export function MenuDemo() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline" />}>Dropdown menu</MenuTrigger>
<MenuPopup>
<MenuGroup>
<MenuGroupLabel>View</MenuGroupLabel>
<MenuCheckboxItem>Sidebar</MenuCheckboxItem>
<MenuCheckboxItem>Status bar</MenuCheckboxItem>
<MenuCheckboxItem>Activity bar</MenuCheckboxItem>
</MenuGroup>
<MenuGroup>
<MenuGroupLabel>Theme</MenuGroupLabel>
<MenuRadioGroup>
<MenuRadioItem indicatorPosition="start" value="light">
Light
</MenuRadioItem>
<MenuRadioItem indicatorPosition="start" value="dark">
Dark
</MenuRadioItem>
<MenuRadioItem indicatorPosition="start" value="system">
System
</MenuRadioItem>
</MenuRadioGroup>
</MenuGroup>
<MenuGroup>
<MenuGroupLabel>Notifications</MenuGroupLabel>
<MenuCheckboxItem variant="switch" indicatorPosition="start">
Email notifications
</MenuCheckboxItem>
<MenuCheckboxItem variant="switch" indicatorPosition="start">
Push notifications
</MenuCheckboxItem>
<MenuCheckboxItem variant="switch" indicatorPosition="start">
SMS notifications
</MenuCheckboxItem>
</MenuGroup>
</MenuPopup>
</Menu>
);
}Menu popup side and align
To change the side and alignment of the menu popup, use the positionerProps prop of MenuPopup and pass the desired side and align values.
Side
Align
import { Button } from "@/components/ui/button";
import { Menu, MenuItem, MenuPopup, MenuTrigger } from "@/components/ui/menu";
export function MenuDemo() {
return (
<div className="flex flex-col gap-5">
<div>
<p className="mb-2">Side</p>
<div className="flex items-center gap-5">
<Menu>
<MenuTrigger render={<Button variant="outline">Top</Button>} />
<MenuPopup positionerProps={{ side: "top" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
<Menu>
<MenuTrigger render={<Button variant="outline">Bottom</Button>} />
<MenuPopup positionerProps={{ side: "bottom" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
<Menu>
<MenuTrigger render={<Button variant="outline">Left</Button>} />
<MenuPopup positionerProps={{ side: "left" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
<Menu>
<MenuTrigger render={<Button variant="outline">Right</Button>} />
<MenuPopup positionerProps={{ side: "right" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
</div>
</div>
<div>
<p className="mb-2">Align</p>
<div className="flex items-center gap-5">
<Menu>
<MenuTrigger render={<Button variant="outline">Start</Button>} />
<MenuPopup positionerProps={{ align: "start" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
<Menu>
<MenuTrigger render={<Button variant="outline">Center</Button>} />
<MenuPopup positionerProps={{ align: "center" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
<Menu>
<MenuTrigger render={<Button variant="outline">End</Button>} />
<MenuPopup positionerProps={{ align: "end" }}>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem>Open recent</MenuItem>
</MenuPopup>
</Menu>
</div>
</div>
</div>
);
}Menu item close on click
By default, menu items close the menu when clicked because for MenuItem the default value of the closeOnClick prop is true.
But for MenuCheckboxItem and MenuRadioItem, the default value of the closeOnClick prop is false.
You can change this behavior by setting the closeOnClick prop to true.
import { Button } from "@/components/ui/button";
import {
Menu,
MenuCheckboxItem,
MenuGroup,
MenuGroupLabel,
MenuPopup,
MenuRadioGroup,
MenuRadioItem,
MenuTrigger,
} from "@/components/ui/menu";
export function MenuDemo() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Settings</Button>} />
<MenuPopup>
<MenuGroup>
<MenuGroupLabel>View</MenuGroupLabel>
<MenuCheckboxItem closeOnClick>Sidebar</MenuCheckboxItem>
<MenuCheckboxItem closeOnClick>Status bar</MenuCheckboxItem>
<MenuCheckboxItem closeOnClick>Activity bar</MenuCheckboxItem>
</MenuGroup>
<MenuGroup>
<MenuGroupLabel>Theme</MenuGroupLabel>
<MenuRadioGroup>
<MenuRadioItem closeOnClick value="light">
Light
</MenuRadioItem>
<MenuRadioItem closeOnClick value="dark">
Dark
</MenuRadioItem>
<MenuRadioItem closeOnClick value="system">
System
</MenuRadioItem>
</MenuRadioGroup>
</MenuGroup>
</MenuPopup>
</Menu>
);
}Menu with dialog
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
} from "@/components/ui/dialog";
import { Menu, MenuItem, MenuPopup, MenuTrigger } from "@/components/ui/menu";
export function MenuDemo() {
const [dialogOpen, setDialogOpen] = useState(false);
return (
<>
<Menu>
<MenuTrigger render={<Button variant="outline" />}>Menu</MenuTrigger>
<MenuPopup>
<MenuItem>New File</MenuItem>
<MenuItem>New Folder</MenuItem>
<MenuItem onClick={() => setDialogOpen(true)}>Open settings</MenuItem>
</MenuPopup>
</Menu>
<Dialog onOpenChange={setDialogOpen} open={dialogOpen}>
<DialogPopup>
<DialogHeader>
<DialogTitle>Settings</DialogTitle>
<DialogDescription>Change your preferences</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Close</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>
</>
);
}API Reference
Menu popup props
Type
booleanDefault
falseType
React.ComponentProps<typeof MenuPositioner>Menu item props
Type
'default' | 'danger'Default
'default'Menu checkbox item props
Type
'default' | 'switch'Default
'default'Type
'start' | 'end'Default
'end'Menu radio item props
Type
'start' | 'end'Default
'end'