import { Menu, MenuTrigger, MenuContent, MenuItem, MenuItemLabel, MenuSeparator } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
export function MenuExample() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuItem>
<MenuItemLabel>Profile</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Settings</MenuItemLabel>
</MenuItem>
<MenuSeparator />
<MenuItem>
<MenuItemLabel>Log out</MenuItemLabel>
</MenuItem>
</MenuContent>
</Menu>
);
} Examples
With icons
Add icons to menu items using MenuItemLeadingIcon and MenuItemTrailingIcon.
import { Menu, MenuTrigger, MenuContent, MenuItem, MenuItemLabel, MenuItemLeadingIcon, MenuSeparator } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
import { GearIcon, UserIcon, SignOutIcon } from "@phosphor-icons/react";
export function MenuWithIconsExample() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuItem>
<MenuItemLeadingIcon>
<UserIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Profile</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLeadingIcon>
<GearIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Settings</MenuItemLabel>
</MenuItem>
<MenuSeparator />
<MenuItem>
<MenuItemLeadingIcon>
<SignOutIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Log out</MenuItemLabel>
</MenuItem>
</MenuContent>
</Menu>
);
} With groups
Group related items with MenuGroup and MenuGroupLabel.
import { Menu, MenuTrigger, MenuContent, MenuItem, MenuItemLabel, MenuItemLeadingIcon, MenuGroup, MenuGroupLabel, MenuSeparator } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
import { UserIcon, BellIcon, ShieldIcon, FileTextIcon } from "@phosphor-icons/react";
export function MenuWithGroupsExample() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuGroup>
<MenuGroupLabel>Account</MenuGroupLabel>
<MenuItem>
<MenuItemLeadingIcon>
<UserIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Profile</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLeadingIcon>
<BellIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Notifications</MenuItemLabel>
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuGroupLabel>Preferences</MenuGroupLabel>
<MenuItem>
<MenuItemLeadingIcon>
<ShieldIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Privacy</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLeadingIcon>
<FileTextIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Terms</MenuItemLabel>
</MenuItem>
</MenuGroup>
</MenuContent>
</Menu>
);
} Checkbox items
Use MenuCheckboxItem for toggleable options.
import { Menu, MenuTrigger, MenuContent, MenuItemLabel, MenuCheckboxItem } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
import { useState } from "react";
export function MenuCheckboxExample() {
const [showStatusBar, setShowStatusBar] = useState(true);
const [showActivityBar, setShowActivityBar] = useState(false);
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuCheckboxItem checked={showStatusBar} onCheckedChange={setShowStatusBar}>
<MenuItemLabel>Show status bar</MenuItemLabel>
</MenuCheckboxItem>
<MenuCheckboxItem checked={showActivityBar} onCheckedChange={setShowActivityBar}>
<MenuItemLabel>Show activity bar</MenuItemLabel>
</MenuCheckboxItem>
</MenuContent>
</Menu>
);
} Radio items
Use MenuRadioGroup and MenuRadioItem for mutually exclusive options.
import { Menu, MenuTrigger, MenuContent, MenuItemLabel, MenuGroup, MenuGroupLabel, MenuRadioGroup, MenuRadioItem } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
import { useState } from "react";
export function MenuRadioExample() {
const [view, setView] = useState("list");
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuGroup>
<MenuGroupLabel>View</MenuGroupLabel>
<MenuRadioGroup value={view} onValueChange={setView}>
<MenuRadioItem value="list">
<MenuItemLabel>List</MenuItemLabel>
</MenuRadioItem>
<MenuRadioItem value="grid">
<MenuItemLabel>Grid</MenuItemLabel>
</MenuRadioItem>
<MenuRadioItem value="calendar">
<MenuItemLabel>Calendar</MenuItemLabel>
</MenuRadioItem>
</MenuRadioGroup>
</MenuGroup>
</MenuContent>
</Menu>
);
} Destructive item
Use the destructive variant on MenuItem for destructive actions like delete.
import { Menu, MenuTrigger, MenuContent, MenuItem, MenuItemLabel, MenuSeparator } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
export function MenuDestructiveExample() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuItem>
<MenuItemLabel>Edit</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Duplicate</MenuItemLabel>
</MenuItem>
<MenuSeparator />
<MenuItem variant="destructive">
<MenuItemLabel>Delete</MenuItemLabel>
</MenuItem>
</MenuContent>
</Menu>
);
} Submenu
Nest menus with MenuSubmenu and MenuSubmenuTrigger.
import { Menu, MenuTrigger, MenuContent, MenuItem, MenuItemLabel, MenuItemLeadingIcon, MenuItemTrailingIcon, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger } from "@registry/components/ui/menu";
import { Button } from "@registry/components/ui/button";
import { CaretRightIcon, UsersIcon } from "@phosphor-icons/react";
export function MenuSubmenuExample() {
return (
<Menu>
<MenuTrigger render={<Button variant="outline">Menu</Button>} />
<MenuContent>
<MenuItem>
<MenuItemLabel>New file</MenuItemLabel>
</MenuItem>
<MenuSubmenu>
<MenuSubmenuTrigger>
<MenuItemLeadingIcon>
<UsersIcon />
</MenuItemLeadingIcon>
<MenuItemLabel>Share with</MenuItemLabel>
<MenuItemTrailingIcon>
<CaretRightIcon />
</MenuItemTrailingIcon>
</MenuSubmenuTrigger>
<MenuContent>
<MenuItem>
<MenuItemLabel>Team</MenuItemLabel>
</MenuItem>
<MenuItem>
<MenuItemLabel>Organization</MenuItemLabel>
</MenuItem>
<MenuSeparator />
<MenuItem>
<MenuItemLabel>Get link</MenuItemLabel>
</MenuItem>
</MenuContent>
</MenuSubmenu>
<MenuSeparator />
<MenuItem>
<MenuItemLabel>Settings</MenuItemLabel>
</MenuItem>
</MenuContent>
</Menu>
);
} Installation
Copy the source code below into your project:
import { mergeProps, useRender } from "@base-ui/react";
import { Menu as BaseMenu } from "@base-ui/react/menu";
import { cn } from "@registry/lib/utils";
import { CheckIcon } from "@phosphor-icons/react/dist/ssr";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";
function Menu(props: BaseMenu.Root.Props) {
return <BaseMenu.Root data-slot="menu" {...props} />;
}
function MenuTrigger(props: BaseMenu.Trigger.Props) {
return <BaseMenu.Trigger data-slot="menu-trigger" {...props} />;
}
function MenuGroup({ className, ...props }: BaseMenu.Group.Props) {
return (
<BaseMenu.Group
className={cn("col-span-full grid grid-cols-subgrid", className)}
data-slot="menu-group"
{...props}
/>
);
}
function MenuGroupLabel({ className, ...props }: BaseMenu.GroupLabel.Props) {
return (
<BaseMenu.GroupLabel
className={cn("col-start-3 py-1.5 text-sm text-muted-foreground select-none", className)}
data-slot="menu-group-label"
{...props}
/>
);
}
function MenuContent({
className,
positionerProps,
...props
}: {
positionerProps?: BaseMenu.Positioner.Props;
} & BaseMenu.Popup.Props) {
return (
<BaseMenu.Portal>
<BaseMenu.Positioner
{...positionerProps}
align={positionerProps?.align ?? "start"}
alignOffset={positionerProps?.alignOffset ?? -5}
className={cn("outline-none", positionerProps?.className)}
sideOffset={positionerProps?.sideOffset ?? 12}
>
<BaseMenu.Popup
className={cn(
"grid min-w-40 origin-(--transform-origin) grid-cols-[auto_auto_1fr_auto] rounded-xl bg-popover p-1.5 text-popover-foreground shadow-lg outline outline-border transition-[transform,scale,opacity] data-ending-style:scale-90 data-ending-style:opacity-0 data-starting-style:scale-90 data-starting-style:opacity-0 dark:shadow-none",
className,
)}
data-slot="menu-popup"
{...props}
/>
</BaseMenu.Positioner>
</BaseMenu.Portal>
);
}
const menuItemVariants = cva(
"group col-span-full grid cursor-default grid-cols-subgrid items-center p-2 text-sm leading-4 outline-none select-none data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:relative data-highlighted:z-0 data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-lg",
{
defaultVariants: {
variant: "default",
},
variants: {
variant: {
default:
"text-popover-foreground data-highlighted:text-primary-foreground data-highlighted:before:bg-primary [&_svg]:text-muted-foreground data-highlighted:[&_svg]:text-primary-foreground",
destructive:
"text-destructive data-highlighted:text-destructive-foreground data-highlighted:before:bg-destructive [&_svg]:text-destructive/80 data-highlighted:[&_svg]:text-destructive-foreground",
},
},
},
);
export interface MenuItemProps extends BaseMenu.Item.Props, VariantProps<typeof menuItemVariants> {}
function MenuItem({ variant, className, ...props }: MenuItemProps) {
return (
<BaseMenu.Item
className={cn(
menuItemVariants({
className,
variant,
}),
)}
data-slot="menu-item"
{...props}
/>
);
}
export interface MenuLinkItemProps
extends BaseMenu.LinkItem.Props, VariantProps<typeof menuItemVariants> {}
function MenuLinkItem({ variant, className, ...props }: MenuLinkItemProps) {
return (
<BaseMenu.LinkItem
className={cn(
menuItemVariants({
className,
variant,
}),
)}
data-slot="menu-link-item"
{...props}
/>
);
}
function MenuItemLeadingIcon({ className, render, ...props }: useRender.ComponentProps<"span">) {
const iconElement = useRender({
defaultTagName: "span",
props: {
...mergeProps<"span">(
{
className: cn("mr-2 inline-flex size-4 shrink-0", className),
},
props,
),
"data-slot": "menu-item-icon",
},
render,
});
return iconElement;
}
function MenuItemTrailingIcon({ className, render, ...props }: useRender.ComponentProps<"span">) {
const iconElement = useRender({
defaultTagName: "span",
props: {
...mergeProps<"span">(
{
className: cn("ml-2 inline-flex size-4 shrink-0", className),
},
props,
),
"data-slot": "menu-item-trailing-icon",
},
render,
});
return iconElement;
}
function MenuItemLabel({ className, render, ...props }: useRender.ComponentProps<"span">) {
const labelElement = useRender({
defaultTagName: "span",
props: {
...mergeProps<"span">(
{
className: cn("col-start-3 flex items-center", className),
},
props,
),
"data-slot": "menu-item-label",
},
render,
});
return labelElement;
}
function MenuSeparator({ className, ...props }: BaseMenu.Separator.Props) {
return (
<BaseMenu.Separator
className={cn("col-span-full mx-2 my-1.5 h-px bg-border", className)}
data-slot="menu-separator"
{...props}
/>
);
}
function MenuSubmenu(props: BaseMenu.SubmenuRoot.Props) {
return <BaseMenu.SubmenuRoot data-slot="menu-submenu" {...props} />;
}
function MenuSubmenuTrigger({ className, ...props }: BaseMenu.SubmenuTrigger.Props) {
return (
<BaseMenu.SubmenuTrigger
className={cn(
"group col-span-full grid cursor-default grid-cols-subgrid items-center p-2 text-sm leading-4 text-popover-foreground outline-none select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-primary-foreground data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-lg data-highlighted:before:bg-primary",
"data-popup-open:relative data-popup-open:z-0 data-popup-open:before:absolute data-popup-open:before:inset-0 data-popup-open:before:inset-y-0 data-popup-open:before:z-[-1] data-popup-open:before:rounded-lg data-popup-open:before:bg-muted data-highlighted:data-popup-open:text-primary-foreground data-highlighted:data-popup-open:before:bg-primary",
"[&_svg]:text-muted-foreground data-highlighted:[&_svg]:text-primary-foreground",
className,
)}
data-slot="menu-submenu-trigger"
{...props}
/>
);
}
function MenuRadioGroup({ className, ...props }: BaseMenu.RadioGroup.Props) {
return (
<BaseMenu.RadioGroup
className={cn("col-span-full grid grid-cols-subgrid", className)}
data-slot="menu-radio-group"
{...props}
/>
);
}
function MenuRadioItem({ className, children, ...props }: BaseMenu.RadioItem.Props) {
return (
<BaseMenu.RadioItem
className={cn(
"group col-span-full grid cursor-default grid-cols-subgrid items-center p-2 text-sm leading-4 text-popover-foreground outline-none select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-primary-foreground data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-lg data-highlighted:before:bg-primary",
"[&_svg]:text-muted-foreground data-highlighted:[&_svg]:text-primary-foreground",
className,
)}
data-slot="menu-radio-item"
{...props}
>
<MenuRadioItemIndicator />
{children}
</BaseMenu.RadioItem>
);
}
function MenuRadioItemIndicator({ className, ...props }: BaseMenu.RadioItemIndicator.Props) {
return (
<MenuItemLeadingIcon>
<BaseMenu.RadioItemIndicator
className={cn("size-4", className)}
data-slot="menu-radio-item-indicator"
render={<CheckIcon />}
{...props}
/>
</MenuItemLeadingIcon>
);
}
function MenuCheckboxItem({ className, children, ...props }: BaseMenu.CheckboxItem.Props) {
return (
<BaseMenu.CheckboxItem
className={cn(
"group col-span-full grid cursor-default grid-cols-subgrid items-center p-2 text-sm leading-4 text-popover-foreground outline-none select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-primary-foreground data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-lg data-highlighted:before:bg-primary",
"[&_svg]:text-muted-foreground data-highlighted:[&_svg]:text-primary-foreground",
className,
)}
data-slot="menu-checkbox-item"
{...props}
>
<MenuCheckboxItemIndicator />
{children}
</BaseMenu.CheckboxItem>
);
}
function MenuCheckboxItemIndicator({ className, ...props }: BaseMenu.CheckboxItemIndicator.Props) {
return (
<MenuItemLeadingIcon>
<BaseMenu.CheckboxItemIndicator
className={cn("size-4", className)}
data-slot="menu-checkbox-item-indicator"
render={<CheckIcon />}
{...props}
/>
</MenuItemLeadingIcon>
);
}
export {
Menu,
MenuTrigger,
MenuContent,
MenuItem,
MenuLinkItem,
MenuGroup,
MenuGroupLabel,
MenuSeparator,
MenuItemLeadingIcon,
MenuItemLeadingIcon as MenuItemIcon,
MenuItemTrailingIcon,
MenuItemLabel,
MenuSubmenu,
MenuSubmenuTrigger,
MenuRadioGroup,
MenuRadioItem,
MenuRadioItemIndicator,
MenuCheckboxItem,
MenuCheckboxItemIndicator,
}; API Reference
Menu
This component does not add any props on top of Base UI Menu.Root . See the Base UI docs for the full API reference.
MenuTrigger
This component does not add any props on top of Base UI Menu.Trigger . See the Base UI docs for the full API reference.
MenuContent
The MenuContent component extends the Base UI Menu.Popup
props and adds the following:
| Prop | Type | Default | Description |
|---|---|---|---|
| positionerProps | BaseMenu.Positioner.Props | — | Props forwarded to the underlying Positioner component. Includes align, side, sideOffset, alignOffset, collisionPadding, etc. |
MenuItem
The MenuItem component extends the Base UI Menu.Item
props and adds the following:
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "destructive" | "default" | Visual style of the menu item. |
MenuLinkItem
The MenuLinkItem component extends the Base UI Menu.LinkItem
props and adds the following:
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "destructive" | "default" | Visual style of the menu item. |
MenuGroup
This component does not add any props on top of Base UI Menu.Group . See the Base UI docs for the full API reference.
MenuGroupLabel
This component does not add any props on top of Base UI Menu.GroupLabel . See the Base UI docs for the full API reference.
MenuSeparator
This component does not add any props on top of Base UI Menu.Separator . See the Base UI docs for the full API reference.
MenuItemLeadingIcon
| Prop | Type | Default | Description |
|---|---|---|---|
| render | React.ReactElement | ((props, state) => React.ReactElement) | — | Custom element or render function used instead of the default span container. |
MenuItemTrailingIcon
| Prop | Type | Default | Description |
|---|---|---|---|
| render | React.ReactElement | ((props, state) => React.ReactElement) | — | Custom element or render function used instead of the default span container. |
MenuItemLabel
| Prop | Type | Default | Description |
|---|---|---|---|
| render | React.ReactElement | ((props, state) => React.ReactElement) | — | Custom element or render function used instead of the default span container. |
MenuSubmenu
This component does not add any props on top of Base UI Menu.SubmenuRoot . See the Base UI docs for the full API reference.
MenuSubmenuTrigger
This component does not add any props on top of Base UI Menu.SubmenuTrigger . See the Base UI docs for the full API reference.
MenuRadioGroup
This component does not add any props on top of Base UI Menu.RadioGroup . See the Base UI docs for the full API reference.
MenuRadioItem
This component does not add any props on top of Base UI Menu.RadioItem . See the Base UI docs for the full API reference.
MenuCheckboxItem
This component does not add any props on top of Base UI Menu.CheckboxItem . See the Base UI docs for the full API reference.