The Menu component extends Base UI's Menu. It displays a list of actions in a dropdown, triggered by clicking a button. Enhanced with keyboard navigation and accessibility features.
Here is the component implementation code that you can copy to your project:
// https://base-ui.com/react/components/menu
import { Menu as BaseMenu } from "@base-ui-components/react/menu";
import clsx from "clsx";
import { Check, ChevronRight, Circle } from "lucide-react";
import styles from "./Menu.module.scss";
// Root component
const Root = BaseMenu.Root;
// Trigger component
interface TriggerProps extends BaseMenu.Trigger.Props {
className?: string;
}
function Trigger({ className, ...props }: TriggerProps) {
return (
<BaseMenu.Trigger className={clsx(styles.trigger, className)} {...props} />
);
}
// Portal component
const Portal = BaseMenu.Portal;
// Backdrop component
const Backdrop = BaseMenu.Backdrop;
// Positioner component
interface PositionerProps extends BaseMenu.Positioner.Props {
className?: string;
}
function Positioner({ className, ...props }: PositionerProps) {
return (
<BaseMenu.Positioner
className={clsx(styles.positioner, className)}
{...props}
/>
);
}
// Popup component
interface PopupProps extends BaseMenu.Popup.Props {
className?: string;
}
function Popup({ className, ...props }: PopupProps) {
return (
<BaseMenu.Popup className={clsx(styles.popup, className)} {...props} />
);
}
// Item component
interface ItemProps extends BaseMenu.Item.Props {
className?: string;
}
function Item({ className, ...props }: ItemProps) {
return <BaseMenu.Item className={clsx(styles.item, className)} {...props} />;
}
// Separator component
interface SeparatorProps extends BaseMenu.Separator.Props {
className?: string;
}
function Separator({ className, ...props }: SeparatorProps) {
return (
<BaseMenu.Separator
className={clsx(styles.separator, className)}
{...props}
/>
);
}
// Group component
interface GroupProps extends BaseMenu.Group.Props {
className?: string;
}
function Group({ className, ...props }: GroupProps) {
return (
<BaseMenu.Group className={clsx(styles.group, className)} {...props} />
);
}
// GroupLabel component
interface GroupLabelProps extends BaseMenu.GroupLabel.Props {
className?: string;
}
function GroupLabel({ className, ...props }: GroupLabelProps) {
return (
<BaseMenu.GroupLabel
className={clsx(styles.groupLabel, className)}
{...props}
/>
);
}
// RadioGroup component
interface RadioGroupProps extends BaseMenu.RadioGroup.Props {
className?: string;
}
function RadioGroup({ className, ...props }: RadioGroupProps) {
return (
<BaseMenu.RadioGroup
className={clsx(styles.radioGroup, className)}
{...props}
/>
);
}
// RadioItem component
interface RadioItemProps extends BaseMenu.RadioItem.Props {
className?: string;
}
function RadioItem({ className, ...props }: RadioItemProps) {
return (
<BaseMenu.RadioItem
className={clsx(styles.item, styles.radioItem, className)}
{...props}
/>
);
}
// RadioItemIndicator component
interface RadioItemIndicatorProps extends BaseMenu.RadioItemIndicator.Props {
className?: string;
children?: React.ReactNode;
}
function RadioItemIndicator({
className,
children,
...props
}: RadioItemIndicatorProps) {
return (
<BaseMenu.RadioItemIndicator
className={clsx(styles.itemIndicator, className)}
{...props}
>
{children || <Circle size={8} fill="currentColor" />}
</BaseMenu.RadioItemIndicator>
);
}
// CheckboxItem component
interface CheckboxItemProps extends BaseMenu.CheckboxItem.Props {
className?: string;
}
function CheckboxItem({ className, ...props }: CheckboxItemProps) {
return (
<BaseMenu.CheckboxItem
className={clsx(styles.item, styles.checkboxItem, className)}
{...props}
/>
);
}
// CheckboxItemIndicator component
interface CheckboxItemIndicatorProps
extends BaseMenu.CheckboxItemIndicator.Props {
className?: string;
children?: React.ReactNode;
}
function CheckboxItemIndicator({
className,
children,
...props
}: CheckboxItemIndicatorProps) {
return (
<BaseMenu.CheckboxItemIndicator
className={clsx(styles.itemIndicator, className)}
{...props}
>
{children || <Check size={14} strokeWidth={2.5} />}
</BaseMenu.CheckboxItemIndicator>
);
}
// SubmenuRoot component
const SubmenuRoot = BaseMenu.SubmenuRoot;
// SubmenuTrigger component
interface SubmenuTriggerProps extends BaseMenu.SubmenuTrigger.Props {
className?: string;
}
function SubmenuTrigger({ className, ...props }: SubmenuTriggerProps) {
return (
<BaseMenu.SubmenuTrigger
className={clsx(styles.item, styles.submenuTrigger, className)}
{...props}
/>
);
}
// Arrow component for submenu triggers (chevron indicator)
interface ArrowProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
children?: React.ReactNode;
}
function Arrow({ className, children, ...props }: ArrowProps) {
return (
<div className={clsx(styles.arrow, className)} {...props}>
{children || <ChevronRight size={16} />}
</div>
);
}
const Menu = {
Root,
Trigger,
Portal,
Backdrop,
Positioner,
Popup,
Item,
Separator,
Group,
GroupLabel,
RadioGroup,
RadioItem,
RadioItemIndicator,
CheckboxItem,
CheckboxItemIndicator,
SubmenuRoot,
SubmenuTrigger,
Arrow,
};
export default Menu;A basic menu with items and a separator. Use the disabled prop to disable specific items.
Use SubmenuRoot, SubmenuTrigger, and Arrow to create nested menus.
Use Group and GroupLabel to organize items into labeled sections.
Use CheckboxItem and CheckboxItemIndicator for toggleable options within the menu.
Use RadioGroup, RadioItem, and RadioItemIndicator for single-selection options.