Displays a menu located at the pointer, triggered by a right-click or a long-press.
import React from 'react';
import { styled, keyframes } from '@stitches/react';
import { violet, mauve, blackA } from '@radix-ui/colors';
import { DotFilledIcon, CheckIcon, ChevronRightIcon } from '@radix-ui/react-icons';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
const StyledContent = styled(ContextMenuPrimitive.Content, {
minWidth: 220,
backgroundColor: 'white',
borderRadius: 6,
overflow: 'hidden',
padding: 5,
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
});
const itemStyles = {
all: 'unset',
fontSize: 13,
lineHeight: 1,
color: violet.violet11,
borderRadius: 3,
display: 'flex',
alignItems: 'center',
height: 25,
padding: '0 5px',
position: 'relative',
paddingLeft: 25,
userSelect: 'none',
'&[data-disabled]': {
color: mauve.mauve8,
pointerEvents: 'none',
},
'&:focus': {
backgroundColor: violet.violet9,
color: violet.violet1,
},
};
const StyledItem = styled(ContextMenuPrimitive.Item, { ...itemStyles });
const StyledCheckboxItem = styled(ContextMenuPrimitive.CheckboxItem, { ...itemStyles });
const StyledRadioItem = styled(ContextMenuPrimitive.RadioItem, { ...itemStyles });
const StyledTriggerItem = styled(ContextMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: violet.violet4,
color: violet.violet11,
},
...itemStyles,
});
const StyledLabel = styled(ContextMenuPrimitive.Label, {
paddingLeft: 25,
fontSize: 12,
lineHeight: '25px',
color: mauve.mauve11,
});
const StyledSeparator = styled(ContextMenuPrimitive.Separator, {
height: 1,
backgroundColor: violet.violet6,
margin: 5,
});
const StyledItemIndicator = styled(ContextMenuPrimitive.ItemIndicator, {
position: 'absolute',
left: 0,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
});
// Exports
export const ContextMenu = ContextMenuPrimitive.Root;
export const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
export const ContextMenuContent = StyledContent;
export const ContextMenuItem = StyledItem;
export const ContextMenuCheckboxItem = StyledCheckboxItem;
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
export const ContextMenuRadioItem = StyledRadioItem;
export const ContextMenuItemIndicator = StyledItemIndicator;
export const ContextMenuTriggerItem = StyledTriggerItem;
export const ContextMenuLabel = StyledLabel;
export const ContextMenuSeparator = StyledSeparator;
// Your app...
const Box = styled('div', {});
const Instruction = styled('div', {
border: `2px white dashed`,
color: 'white',
borderRadius: 4,
fontSize: 15,
userSelect: 'none',
padding: '45px 0',
width: 300,
textAlign: 'center',
});
const RightSlot = styled('div', {
marginLeft: 'auto',
paddingLeft: 20,
color: mauve.mauve11,
':focus > &': { color: 'white' },
'[data-disabled] &': { color: mauve.mauve8 },
});
export const ContextMenuDemo = () => {
const [bookmarksChecked, setBookmarksChecked] = React.useState(true);
const [urlsChecked, setUrlsChecked] = React.useState(false);
const [person, setPerson] = React.useState('pedro');
return (
<Box>
<ContextMenu>
<ContextMenuTrigger>
<Instruction>Right click here.</Instruction>
</ContextMenuTrigger>
<ContextMenuContent sideOffset={5} align="end">
<ContextMenuItem>
Back <RightSlot>⌘+[</RightSlot>
</ContextMenuItem>
<ContextMenuItem disabled>
Foward <RightSlot>⌘+]</RightSlot>
</ContextMenuItem>
<ContextMenuItem>
Reload <RightSlot>⌘+R</RightSlot>
</ContextMenuItem>
<ContextMenu>
<ContextMenuTriggerItem>
More Tools
<RightSlot>
<ChevronRightIcon />
</RightSlot>
</ContextMenuTriggerItem>
<ContextMenuContent sideOffset={2} alignOffset={-5}>
<ContextMenuItem>
Save Page As… <RightSlot>⌘+S</RightSlot>
</ContextMenuItem>
<ContextMenuItem>Create Shortcut…</ContextMenuItem>
<ContextMenuItem>Name Window…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<ContextMenuSeparator />
<ContextMenuCheckboxItem checked={bookmarksChecked} onCheckedChange={setBookmarksChecked}>
<ContextMenuItemIndicator>
<CheckIcon />
</ContextMenuItemIndicator>
Show Bookmarks <RightSlot>⌘+B</RightSlot>
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem checked={urlsChecked} onCheckedChange={setUrlsChecked}>
<ContextMenuItemIndicator>
<CheckIcon />
</ContextMenuItemIndicator>
Show Full URLs
</ContextMenuCheckboxItem>
<ContextMenuSeparator />
<ContextMenuLabel>People</ContextMenuLabel>
<ContextMenuRadioGroup value={person} onValueChange={setPerson}>
<ContextMenuRadioItem value="pedro">
<ContextMenuItemIndicator>
<DotFilledIcon />
</ContextMenuItemIndicator>
Pedro Duarte
</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">
<ContextMenuItemIndicator>
<DotFilledIcon />
</ContextMenuItemIndicator>
Colm Tuite
</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent>
</ContextMenu>
</Box>
);
};
export default ContextMenuDemo;
Install the component from your command line.
npm install @radix-ui/react-context-menu
Import all parts and piece them together.
import * as ContextMenu from '@radix-ui/react-context-menu';
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger />
<ContextMenu.Content>
<ContextMenu.Label />
<ContextMenu.Item />
<ContextMenu.Group>
<ContextMenu.Item />
</ContextMenu.Group>
<ContextMenu.CheckboxItem>
<ContextMenu.ItemIndicator />
</ContextMenu.CheckboxItem>
<ContextMenu.RadioGroup>
<ContextMenu.RadioItem>
<ContextMenu.ItemIndicator />
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
<ContextMenu.Root>
<ContextMenu.TriggerItem />
<ContextMenu.Content />
</ContextMenu.Root>
<ContextMenu.Separator />
</ContextMenu.Content>
</ContextMenu.Root>
);
Adheres to the Menu WAI-ARIA design pattern and uses roving tabindex to manage focus movement among menu items.
Contains all the parts of a context menu.
Prop | Type | Default |
---|---|---|
onOpenChange | function | |
modal | boolean | true |
The area that opens the context menu. Wrap it around the target you want the context menu to open from when right-clicking (or using the relevant keyboard shortcuts).
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
The component that pops out in an open context menu.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
allowPinchZoom | boolean | false |
loop | boolean | false |
dir | enum | "ltr" |
onCloseAutoFocus | function | |
onEscapeKeyDown | function | |
onPointerDownOutside | function | |
onFocusOutside | function | |
onInteractOutside | function | |
forceMount | boolean | |
sideOffset | number | 0 |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionTolerance | number | 0 |
An optional arrow element to render alongside a submenu. This can be used to help visually link the trigger item with the ContextMenu.Content
. Must be rendered inside ContextMenu.Content
.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
width | number | 10 |
height | number | 5 |
offset | number |
The component that contains the context menu items.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
disabled | boolean | |
onSelect | function | |
textValue | string |
An item that opens a submenu. Used in combination with a nested ContextMenu
. Must be rendered inside ContextMenu.Root
.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
disabled | boolean | |
textValue | string |
Used to group multiple ContextMenu.Item
s.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Used to render a label. It won't be focusable using arrow keys.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
An item that can be controlled and rendered like a checkbox.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
checked | boolean | |
onCheckedChange | function | |
disabled | boolean | |
onSelect | function | |
textValue | string |
Used to group multiple ContextMenu.RadioItem
s.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
value | string | |
onValueChange | function |
An item that can be controlled and rendered like a radio.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
value* | string | |
disabled | boolean | |
onSelect | function | |
textValue | string |
Renders when the parent ContextMenu.CheckboxItem
or ContextMenu.RadioItem
is checked. You can style this element directly, or you can use it as a wrapper to put an icon into, or both.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
forceMount | boolean |
Used to visually separate items in the context menu.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
You can create submenus by nesting ContextMenu
s and using a TriggerItem
in place of a Trigger
.
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Root>
<ContextMenu.TriggerItem>Sub menu →</ContextMenu.TriggerItem>
<ContextMenu.Content>
<ContextMenu.Item>Sub menu item</ContextMenu.Item>
<ContextMenu.Item>Sub menu item</ContextMenu.Item>
<ContextMenu.Arrow />
</ContextMenu.Content>
</ContextMenu.Root>
<ContextMenu.Separator />
<ContextMenu.Item>…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
You can add special styles to disabled items via the data-disabled
attribute.
import { styled } from '@stitches/react';
import * as ContextMenu from '@radix-ui/react-context-menu';
const StyledItem = styled(ContextMenu.Item, {
'&[data-disabled]': { color: 'gainsboro' },
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<StyledItem disabled>…</StyledItem>
<StyledItem>…</StyledItem>
<StyledItem>…</StyledItem>
</ContextMenu.Content>
</ContextMenu.Root>
);
Use the Separator
part to add a separator between items.
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item>…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
Use the Label
part to help label a section.
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Label>Label</ContextMenu.Label>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
Use the CheckboxItem
part to add an item that can be checked.
import React from 'react';
import { CheckIcon } from '@radix-ui/react-icons';
import * as ContextMenu from '@radix-ui/react-context-menu';
export default () => {
const [checked, setChecked] = React.useState(true);
return (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Item>…</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.CheckboxItem
checked={checked}
onCheckedChange={setChecked}
>
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Checkbox item
</ContextMenu.CheckboxItem>
</ContextMenu.Content>
</ContextMenu.Root>
);
};
Use the RadioGroup
and RadioItem
parts to add an item that can be checked amongst others.
import React from 'react';
import { CheckIcon } from '@radix-ui/react-icons';
import * as ContextMenu from '@radix-ui/react-context-menu';
export default () => {
const [color, setColor] = React.useState('blue');
return (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.RadioGroup value={color} onValueChange={setColor}>
<ContextMenu.RadioItem value="red">
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Red
</ContextMenu.RadioItem>
<ContextMenu.RadioItem value="blue">
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Blue
</ContextMenu.RadioItem>
<ContextMenu.RadioItem value="green">
<ContextMenu.ItemIndicator>
<CheckIcon />
</ContextMenu.ItemIndicator>
Green
</ContextMenu.RadioItem>
</ContextMenu.RadioGroup>
</ContextMenu.Content>
</ContextMenu.Root>
);
};
You can add extra decorative elements in the Item
parts, such as images.
import { styled } from '@stitches/react';
import * as ContextMenu from '@radix-ui/react-context-menu';
const Image = styled('img', {
width: 24,
height: 24,
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<ContextMenu.Content>
<ContextMenu.Item>
<Image src="…" />
Adolfo Hess
</ContextMenu.Item>
<ContextMenu.Item>
<Image src="…" />
Miyah Myles
</ContextMenu.Item>
<ContextMenu.Item>
<Image src="…" />
Sylvia Reynolds
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
);
We expose a CSS custom property --radix-context-menu-content-transform-origin
. Use it to animate the content from its computed origin based on side
, sideOffset
, align
, alignOffset
and any collisions.
import { styled, keyframes } from '@stitches/react';
import * as ContextMenu from '@radix-ui/react-context-menu';
const scaleIn = keyframes({
'0%': { opacity: 0, transform: 'scale(0)' },
'100%': { opacity: 1, transform: 'scale(1)' },
});
const StyledContent = styled(ContextMenu.Content, {
transformOrigin: 'var(--radix-context-menu-content-transform-origin)',
animation: `${scaleIn} 0.5s ease-out forwards`,
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<StyledContent>…</StyledContent>
</ContextMenu.Root>
);
We expose data-side
and data-align
attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.
import { styled, keyframes } from '@stitches/react';
import * as ContextMenu from '@radix-ui/react-context-menu';
const slideDown = keyframes({
'0%': { opacity: 0, transform: 'translateY(-10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideUp = keyframes({
'0%': { opacity: 0, transform: 'translateY(10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const StyledContent = styled(ContextMenu.Content, {
animationDuration: '0.6s',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
animationFillMode: 'forwards',
'&[data-side="top"]': { animationName: slideUp },
'&[data-side="bottom"]': { animationName: slideDown },
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>…</ContextMenu.Trigger>
<StyledContent>…</StyledContent>
</ContextMenu.Root>
);
Uses roving tabindex to manage focus movement among menu items.
Key | Description |
---|---|
Space | Activates the focused item. |
Enter | Activates the focused item. |
ArrowDown | Moves focus to the next item. |
ArrowUp | Moves focus to the previous item. |
ArrowRightArrowLeft | When focus is on ContextMenu.TriggerItem , opens or closes the submenu depending on reading direction. |
Esc | Closes the context menu |