ContextMenu
ContextMenu provides a fully accessible, keyboard-navigable right-click menu for contextual actions. It supports separators, icons, danger items, disabled states, and renders via a portal.
Usageβ
Basic usageβ
import { ContextMenu } from '@nofinite/nui';
<ContextMenu
trigger={<div>Right-click me</div>}
items={[
{ label: 'Edit', onSelect: () => {} },
{ label: 'Rename', onSelect: () => {} },
]}
/>;
With icons, separators, and danger itemsβ
import { ContextMenu, Button } from '@nofinite/nui';
<ContextMenu
trigger={<Button>Options</Button>}
items={[
{ label: 'Open', icon: 'π' },
{ label: 'Copy', icon: 'π' },
{ type: 'separator' },
{
label: 'Delete',
icon: 'π',
danger: true,
onSelect: () => alert('Delete'),
},
]}
/>;
Variantsβ
ContextMenu does not expose visual variants directly. Styling is controlled via CSS and the optional className prop.
Guidelines:
- Use
danger: truefor destructive actions. - Keep labels short and action-oriented.
- Group related actions using separators.
Sizesβ
ContextMenu does not support size props.
Sizing is controlled via CSS (min-width, padding, font size). You may override styles using a custom class.
Statesβ
Each menu item supports multiple states:
{
label: "Delete",
danger: true,
disabled: false,
onSelect: () => {}
}
-
disabled
- Item is skipped during keyboard navigation
- Clicks are ignored
- Visual opacity is reduced
-
danger
- Item is styled as destructive
- Intended for irreversible actions
Native Props / Compositionβ
Triggerβ
The trigger prop accepts any ReactNode and listens for the contextmenu (right-click) event.
<ContextMenu
trigger={<div className="custom-trigger">Right click</div>}
items={items}
/>
Custom stylingβ
<ContextMenu
className="my-contextmenu"
trigger={<div>Trigger</div>}
items={items}
/>
Propsβ
ContextMenuβ
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactNode | β | Element that opens the context menu on right-click |
items | MenuItem[] | β | Menu configuration array |
className | string | "" | Additional class name for menu container |
MenuItemβ
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | β | Text label for the item |
icon | ReactNode | β | Optional leading icon |
onSelect | () => void | β | Called when item is selected |
danger | boolean | false | Marks item as destructive |
disabled | boolean | false | Disables interaction and navigation |
type | "item" | "separator" | "item" | Renders a separator instead of item |
Behavior Notesβ
-
Opens on right-click (
contextmenu) only -
Automatically positions at cursor location
-
Closes when:
- Clicking outside
- Pressing
Escape - Selecting an item
-
Uses roving active index for keyboard navigation
-
Disabled and separator items are skipped during navigation
Accessibilityβ
-
Renders with
role="menu"androle="menuitem" -
Keyboard support:
ArrowUp/ArrowDownβ navigate itemsHome/Endβ jump to first/last enabled itemEnter/Spaceβ activate itemEscapeβ close menu
-
Disabled items use
aria-disabled -
Focus is managed internally for predictable navigation
Layoutβ
- Rendered in a portal (outside normal DOM flow)
- Uses
position: fixedfor accurate cursor placement - Menu width adapts to content (
min-width: 180pxby default)
<ContextMenu
trigger={<div style={{ width: '100%' }}>Full-width trigger</div>}
items={items}
/>
Best Practicesβ
Doβ
- Use for contextual, secondary actions
- Group related actions with separators
- Mark destructive actions with
danger - Keep menus short and focused
Donβtβ
- Use as a primary navigation menu
- Overload with too many options
- Place critical actions without confirmation
- Rely on icons without text labels