The ContextMenu component extends Base UI's ContextMenu with polished default styles. It displays a menu of contextual actions triggered by right-clicking (or long-pressing on touch devices) on an element. We added custom subcomponents: SubmenuChevron for submenu trigger icons, and Shortcut for displaying keyboard shortcuts alongside menu items.
Here is the component implementation code that you can copy to your project:
// https://devie-ui.com/components/context-menu
// https://base-ui.com/react/components/context-menu
import { ContextMenu as BaseContextMenu } from "@base-ui/react/context-menu";
import clsx from "clsx";
import { Check, ChevronRight, Circle } from "lucide-react";
import type React from "react";
import styles from "./ContextMenu.module.scss";
const Root = BaseContextMenu.Root;
function Trigger({ className, ...props }: BaseContextMenu.Trigger.Props) {
return (
<BaseContextMenu.Trigger
className={clsx(styles.trigger, className)}
{...props}
/>
);
}
const Portal = BaseContextMenu.Portal;
const Backdrop = BaseContextMenu.Backdrop;
function Positioner({ className, ...props }: BaseContextMenu.Positioner.Props) {
return (
<BaseContextMenu.Positioner
className={clsx(styles.positioner, className)}
{...props}
/>
);
}
function Popup({ className, ...props }: BaseContextMenu.Popup.Props) {
return (
<BaseContextMenu.Popup
className={clsx(styles.popup, className)}
{...props}
/>
);
}
function Arrow({ className, ...props }: BaseContextMenu.Arrow.Props) {
return (
<BaseContextMenu.Arrow
className={clsx(styles.arrow, className)}
{...props}
/>
);
}
function Item({ className, ...props }: BaseContextMenu.Item.Props) {
return (
<BaseContextMenu.Item className={clsx(styles.item, className)} {...props} />
);
}
function LinkItem({ className, ...props }: BaseContextMenu.LinkItem.Props) {
return (
<BaseContextMenu.LinkItem
className={clsx(styles.item, className)}
{...props}
/>
);
}
function Separator({ className, ...props }: BaseContextMenu.Separator.Props) {
return (
<BaseContextMenu.Separator
className={clsx(styles.separator, className)}
{...props}
/>
);
}
function Group({ className, ...props }: BaseContextMenu.Group.Props) {
return (
<BaseContextMenu.Group
className={clsx(styles.group, className)}
{...props}
/>
);
}
function GroupLabel({ className, ...props }: BaseContextMenu.GroupLabel.Props) {
return (
<BaseContextMenu.GroupLabel
className={clsx(styles.groupLabel, className)}
{...props}
/>
);
}
function RadioGroup({ className, ...props }: BaseContextMenu.RadioGroup.Props) {
return (
<BaseContextMenu.RadioGroup
className={clsx(styles.radioGroup, className)}
{...props}
/>
);
}
function RadioItem({ className, ...props }: BaseContextMenu.RadioItem.Props) {
return (
<BaseContextMenu.RadioItem
className={clsx(styles.item, styles.radioItem, className)}
{...props}
/>
);
}
function RadioItemIndicator({
className,
children,
...props
}: BaseContextMenu.RadioItemIndicator.Props) {
return (
<BaseContextMenu.RadioItemIndicator
className={clsx(styles.itemIndicator, className)}
{...props}
>
{children || <Circle size={8} fill="currentColor" />}
</BaseContextMenu.RadioItemIndicator>
);
}
function CheckboxItem({
className,
...props
}: BaseContextMenu.CheckboxItem.Props) {
return (
<BaseContextMenu.CheckboxItem
className={clsx(styles.item, styles.checkboxItem, className)}
{...props}
/>
);
}
function CheckboxItemIndicator({
className,
children,
...props
}: BaseContextMenu.CheckboxItemIndicator.Props) {
return (
<BaseContextMenu.CheckboxItemIndicator
className={clsx(styles.itemIndicator, className)}
{...props}
>
{children || <Check size={16} strokeWidth={1.5} />}
</BaseContextMenu.CheckboxItemIndicator>
);
}
const SubmenuRoot = BaseContextMenu.SubmenuRoot;
function SubmenuTrigger({
className,
...props
}: BaseContextMenu.SubmenuTrigger.Props) {
return (
<BaseContextMenu.SubmenuTrigger
className={clsx(styles.item, styles.submenuTrigger, className)}
{...props}
/>
);
}
interface SubmenuChevronProps extends React.HTMLAttributes<HTMLDivElement> {
className?: string;
children?: React.ReactNode;
}
function SubmenuChevron({
className,
children,
...props
}: SubmenuChevronProps) {
return (
<div className={clsx(styles.submenuChevron, className)} {...props}>
{children || <ChevronRight size={16} />}
</div>
);
}
interface ShortcutProps extends React.HTMLAttributes<HTMLElement> {
className?: string;
}
function Shortcut({ className, ...props }: ShortcutProps) {
return <kbd className={clsx(styles.shortcut, className)} {...props} />;
}
const ContextMenu = {
Root,
Trigger,
Portal,
Backdrop,
Positioner,
Popup,
Arrow,
Item,
LinkItem,
Separator,
Group,
GroupLabel,
RadioGroup,
RadioItem,
RadioItemIndicator,
CheckboxItem,
CheckboxItemIndicator,
SubmenuRoot,
SubmenuTrigger,
SubmenuChevron,
Shortcut,
};
export default ContextMenu;A basic context menu with items and a separator. Use the disabled prop to disable specific items.
Use LinkItem for context menu items that navigate to a URL. It renders an <a> element and supports the closeOnClick prop to close the menu when clicked.
Use Shortcut to display keyboard shortcut hints alongside menu items.
Use SubmenuRoot, SubmenuTrigger, and SubmenuChevron 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.