Devie UI

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.

Installation

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;

Use Cases

Simple 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.

With groups

Use Group and GroupLabel to organize items into labeled sections.

With checkbox items

Use CheckboxItem and CheckboxItemIndicator for toggleable options within the menu.

With radio items

Use RadioGroup, RadioItem, and RadioItemIndicator for single-selection options.