Context Menu
- Stable
- WCAG 2.2 evidence
- RTL
- Web · iOS · Android
The menu of actions for an element, opened where the user asks for it: right-click, long-press
on touch screens, and from the keyboard with Shift+F10 or the ContextMenu key (the browser reports both as a
contextmenu event on the focused element; on macOS, Control+Enter). Built on React Aria MenuTrigger
(trigger="contextMenu") + Popover + Menu, so the menu keyboard model applies: arrows, Home/End, type-ahead, Enter;
Escape closes it and returns focus to the element (WCAG 2.1.1, 2.4.3, 4.1.2). Right-clicking outside closes it. A context menu is hard to discover: never make it the only way to reach an action; also offer visible row actions
or a Menu button.
import { ContextMenu } from "@nexera-ui/react";- 4
- examples
- 31
- props
- 2
- live controls
- 3
- platforms
- 4
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { ContextMenu } from "@nexera-ui/react";
<ContextMenu />Examples 3
The same examples as Storybook, rendered live. Open Code to copy one.
With header
Figma Type=With header: the header ("3 people selected") also names the menu.
import { ContextMenu, MenuDivider, MenuItem, cn } from "@nexera-ui/react";
import { LuDownload, LuMail, LuTrash2, LuUsers } from "react-icons/lu";
const targetClass = cn(
"box-border inline-flex min-h-20 w-72 max-w-full items-center gap-3 rounded-md border border-default bg-surface p-4 text-body-default text-primary",
focusRingNative,
);
export function WithHeader() {
return (
<ContextMenu
type="with-header"
header="3 people selected"
trigger={
<div role="button" tabIndex={0} className={targetClass}>
<LuUsers className="size-5 shrink-0 text-secondary" />3 people selected
</div>
}
>
<MenuItem id="message" label="Send message" icon={<LuMail />} />
<MenuItem id="export" label="Export" icon={<LuDownload />} />
<MenuDivider />
<MenuItem id="remove" label="Remove from team" type="destructive" icon={<LuTrash2 />} />
</ContextMenu>
);
}
With quick actions
Figma Type=With quick actions: a named group of tiles above the items; the arrow keys reach every tile.
import { ContextMenu, Menu, MenuDivider, MenuItem, SubmenuTrigger, cn } from "@nexera-ui/react";
import {
LuCopy,
LuDownload,
LuExternalLink,
LuFileText,
LuLink,
LuMail,
LuPencil,
LuShare2,
LuStar,
LuTrash2,
LuUsers,
} from "react-icons/lu";
const quickActions: readonly ContextMenuQuickAction[] = [
{ id: "copy", label: "Copy", icon: <LuCopy /> },
{ id: "share", label: "Share", icon: <LuShare2 /> },
{ id: "star", label: "Star", icon: <LuStar /> },
{ id: "remove", label: "Delete", icon: <LuTrash2 />, type: "destructive" },
];
const targetClass = cn(
"box-border inline-flex min-h-20 w-72 max-w-full items-center gap-3 rounded-md border border-default bg-surface p-4 text-body-default text-primary",
focusRingNative,
);
function Items() {
return (
<>
<MenuItem id="open" label="Open" icon={<LuExternalLink />} shortcut="O" />
<MenuItem id="rename" label="Rename" icon={<LuPencil />} />
<SubmenuTrigger>
<MenuItem id="share" label="Share with" icon={<LuUsers />} />
<Menu>
<MenuItem id="email" label="Email" icon={<LuMail />} />
<MenuItem id="link" label="Copy link" icon={<LuLink />} />
</Menu>
</SubmenuTrigger>
<MenuItem id="download" label="Download" icon={<LuDownload />} />
<MenuDivider />
<MenuItem id="delete" label="Delete" type="destructive" icon={<LuTrash2 />} />
</>
);
}
export function WithQuickActions() {
return (
<ContextMenu
type="with-quick-actions"
quickActions={quickActions}
trigger={
<div role="button" tabIndex={0} className={targetClass}>
<LuFileText className="size-5 shrink-0 text-secondary" />
Q3 report.pdf
</div>
}
>
<Items />
</ContextMenu>
);
}
Platforms
platform="ios" and "android" open the Figma System menu surface.
import { ContextMenu, MenuDivider, MenuItem, cn } from "@nexera-ui/react";
import { LuCopy, LuShare2, LuTrash2 } from "react-icons/lu";
const platforms = ["web", "ios", "android"] as const;
const targetClass = cn(
"box-border inline-flex min-h-20 w-72 max-w-full items-center gap-3 rounded-md border border-default bg-surface p-4 text-body-default text-primary",
focusRingNative,
);
export function Platforms() {
return (
<div className="flex flex-wrap gap-4">
{platforms.map((platform) => (
<ContextMenu
key={platform}
platform={platform}
trigger={
<div role="button" tabIndex={0} className={targetClass}>
{`Long-press or right-click (${platform})`}
</div>
}
>
<MenuItem id="share" label="Share" icon={<LuShare2 />} />
<MenuItem id="copy" label="Copy" icon={<LuCopy />} />
<MenuDivider />
<MenuItem id="delete" label="Delete" type="destructive" icon={<LuTrash2 />} />
</ContextMenu>
))}
</div>
);
}
Props 31
Press "Try it" on a card to load that prop into the playground.
31 props shown
trigger*NexeraReactElement<DOMAttributes<FocusableElement>, string>The element the menu belongs to: right-click, long-press (touch), Shift+F10 or the ContextMenu key on it opens the
menu at that point. It must be a DOM element that can take keyboard focus (tabIndex={0}) and has an interactive
role, for example a row or a card with role="button", so keyboard users can open the menu too (WCAG 2.1.1).
isOpenNexerabooleanWhether the menu is open (controlled). Use with onOpenChange.
defaultOpenNexerabooleanWhether the menu is open on first render (uncontrolled). It then opens at the trigger.
onOpenChangeNexera(isOpen: boolean) => voidCalled when the menu opens or closes.
platformNexera"web" | "ios" | "android"Platform look. iOS and Android use the Figma System menu surface and rows. Defaults to the NexeraProvider.
classNameNexerastringExtra classes for the menu surface, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style for the menu surface.
typeNexera"default" | "with-header" | "with-quick-actions"Layout: default lists the items; with-header adds a heading such as "3 people selected";
with-quick-actions adds a row of icon tiles above the items.
headerNexeraReactNodeHeading above the items, for example "3 people selected". It also names the menu.
quickActionsNexerareadonly ContextMenuQuickAction[]Tiles above the items: up to four frequent actions such as Copy, Share, Star and Delete.
quickActionsLabelNexerastringAccessible name of the quick-actions group. Translate it for your locale.
childrenReact AriaReactNode | ((item: T) => ReactNode)The contents of the collection.
escapeKeyBehaviorReact Aria"clearSelection" | "none"Whether pressing the escape key should clear selection in the menu or not. Most experiences should not modify this option as it eliminates a keyboard user's ability to easily clear selection. Only use if the escape key is being handled externally or should not trigger selection clearing contextually.
autoFocusReact Ariaboolean | FocusStrategyWhere the focus should be set.
shouldFocusWrapReact AriabooleanWhether keyboard navigation is circular.
onActionReact Aria(key: Key, value: T) => voidHandler that is called when an item is selected.
onCloseReact Aria() => voidHandler that is called when the menu should close after selecting an item.
itemsReact AriaIterable<T>Item objects in the collection.
disabledKeysReact AriaIterable<Key>The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
selectionModeReact Aria"none" | "single" | "multiple"The type of selection that is allowed in the collection.
disallowEmptySelectionReact AriabooleanWhether the collection allows empty selection.
selectedKeysReact AriaIterable<Key> | "all"The currently selected keys in the collection (controlled).
defaultSelectedKeysReact AriaIterable<Key> | "all"The initial selected keys in the collection (uncontrolled).
onSelectionChangeReact Aria(keys: Selection) => voidHandler that is called when the selection changes.
idReact AriastringThe element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
aria-labelReact AriastringDefines a string value that labels the current element.
aria-labelledbyReact AriastringIdentifies the element (or elements) that labels the current element.
aria-describedbyReact AriastringIdentifies the element (or elements) that describes the object.
renderEmptyStateReact Aria() => ReactNodeProvides content to display when there are no items in the menu.
shouldCloseOnSelectReact AriabooleanWhether the menu should close when the menu item is selected.
dependenciesReact Ariareadonly any[]Values that should invalidate the item cache when using dynamic collections.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
4 direct · 0 supporting- 1.3.1Info and RelationshipsLevel A · tested directly
- 2.1.1KeyboardLevel A · tested directly
- 2.4.3Focus OrderLevel A · tested directly
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-hovereddata-pressed
<ContextMenu className="data-disabled:opacity-90 shadow-sm" />Used in blocks
Related components
- BoardCardA Kanban card: avatar, name, role, drag handle and a detail line.
- ButtonTriggers an action such as Save, Submit, Approve or Delete.
- ButtonGroupItemOne toggle in a joined row of buttons, for example a List / Grid view switch or a set of filters.
- DragHandleGrip that starts a drag of the collection item it sits in.
- DropIndicatorInsertion line shown between two items while something is dragged over a `GridList` or `ListBox`.
- IconButtonA square, icon-only button for compact actions such as Settings, Close or Delete row.
- MenuA list of actions or options that opens from a trigger.
- MenuDividerSeparates groups of items in a `Menu` or `ContextMenu`.