Context Menu

Displays a menu located at the pointer, triggered by a right-click.

Features

  • Supports items, labels, groups of items.
  • Supports checkable items (single or multiple).
  • Customize side, alignment, offsets, collision handling.
  • Focus is fully managed.
  • Full keyboard navigation.
  • Typeahead support.
  • Dismissing and layering behavior is highly customizable.

Install the component from your command line.

npm install @radix-ui/react-context-menu

Import the components and piece the parts 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.Separator />
</ContextMenu.Content>
</ContextMenu.Root>
);

Create your styled context menu component from the primitive parts.

Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(ContextMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledItem onSelect={() => console.log('cut')}>Cut</StyledItem>
<StyledItem onSelect={() => console.log('copy')}>
Copy
</StyledItem>
<StyledItem onSelect={() => console.log('paste')}>
Paste
</StyledItem>
</StyledContent>
</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.

PropTypeDefault
onOpenChangefunctionNo default value

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

PropTypeDefault
asenumspan

The component that pops out in an open context menu.

PropTypeDefault
asenumdiv
loopbooleanfalse
onCloseAutoFocusfunctionNo default value
disableOutsidePointerEventsbooleantrue
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
forceMountbooleanNo default value
sideenum"bottom"
sideOffsetnumber0
alignenum"center"
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionToleranceboolean0

The component that contains the context menu items.

PropTypeDefault
asenumdiv
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value

Used to group multiple ContextMenu.Items.

PropTypeDefault
asenumdiv

Used to render a label. It won't be focusable using arrow keys.

PropTypeDefault
asenumdiv

An item that can be controlled and rendered like a checkbox.

PropTypeDefault
asenumdiv
checkedbooleanNo default value
onCheckedChangefunctionNo default value
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value

Used to group multiple ContextMenu.RadioItems.

PropTypeDefault
asenumdiv
valuestringNo default value
onValueChangefunctionNo default value

An item that can be controlled and rendered like a radio.

PropTypeDefault
asenumdiv
value*stringNo default value
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value

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.

PropTypeDefault
asenumspan
forceMountbooleanNo default value

Used to visually separate items in the context menu.

PropTypeDefault
asenumdiv

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.

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`,
});

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.

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, {
'&[data-side="top"]': { animationName: slideUp },
'&[data-side="bottom"]': { animationName: slideDown },
animationDuration: '0.6s',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
});

You can add special styles to disabled items via the data-disabled attribute.

Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(ContextMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&[data-disabled]': {
color: 'gainsboro',
},
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledItem disabled onSelect={() => console.log('cut')}>
Cut
</StyledItem>
<StyledItem onSelect={() => console.log('copy')}>
Copy
</StyledItem>
<StyledItem onSelect={() => console.log('paste')}>
Paste
</StyledItem>
</StyledContent>
</ContextMenu.Root>
);

Use the Separator part to add a separator between items.

Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(ContextMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const StyledSeparator = styled(ContextMenu.Separator, {
height: 1,
backgroundColor: 'gainsboro',
margin: 5,
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledItem disabled onSelect={() => console.log('cut')}>
Cut
</StyledItem>
<StyledSeparator />
<StyledItem onSelect={() => console.log('copy')}>
Copy
</StyledItem>
<StyledSeparator />
<StyledItem onSelect={() => console.log('paste')}>
Paste
</StyledItem>
</StyledContent>
</ContextMenu.Root>
);

Use the Label part to help label a section.

Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(ContextMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const StyledLabel = styled(ContextMenu.Label, {
color: 'slategray',
fontSize: 13,
padding: '5px 10px',
cursor: 'default',
});
const StyledSeparator = styled(ContextMenu.Separator, {
height: 1,
backgroundColor: 'gainsboro',
margin: 5,
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledLabel>Actions</StyledLabel>
<StyledItem disabled onSelect={() => console.log('cut')}>
Cut
</StyledItem>
<StyledItem onSelect={() => console.log('copy')}>
Copy
</StyledItem>
<StyledItem onSelect={() => console.log('paste')}>
Paste
</StyledItem>
</StyledContent>
</ContextMenu.Root>
);

Use the CheckboxItem part to add an item that can be checked.

Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
import { CheckIcon } from '@radix-ui/react-icons';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const itemStyles = {
fontSize: 13,
padding: '5px 10px 5px 25px',
borderRadius: 3,
cursor: 'default',
position: 'relative',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
};
const StyledItem = styled(ContextMenu.Item, itemStyles);
const StyledCheckboxItem = styled(
ContextMenu.CheckboxItem,
itemStyles
);
const StyledItemIndicator = styled(ContextMenu.ItemIndicator, {
position: 'absolute',
left: 5,
});
const StyledSeparator = styled(ContextMenu.Separator, {
height: 1,
backgroundColor: 'gainsboro',
margin: 5,
});
export default () => {
const [checked, setChecked] = React.useState(true);
return (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledItem onSelect={() => console.log('radix-ui')}>
About Radix UI
</StyledItem>
<StyledItem onSelect={() => console.log('check-for-updates')}>
Check for updates
</StyledItem>
<StyledSeparator />
<StyledCheckboxItem checked={checked} onCheckedChange={setChecked} >
<StyledItemIndicator>
<CheckIcon />
</StyledItemIndicator>
Show hidden files
</StyledCheckboxItem>
</StyledContent>
</ContextMenu.Root>
);
};

Use the RadioGroup and RadioItem parts to add an item that can be checked amongst others.

Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
import { CheckIcon } from '@radix-ui/react-icons';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const itemStyles = {
fontSize: 13,
padding: '5px 10px 5px 25px',
borderRadius: 3,
cursor: 'default',
position: 'relative',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
};
const StyledItem = styled(ContextMenu.Item, itemStyles);
const StyledRadioGroup = styled(ContextMenu.RadioGroup, {});
const StyledRadioItem = styled(ContextMenu.RadioItem, itemStyles);
const StyledItemIndicator = styled(ContextMenu.ItemIndicator, {
position: 'absolute',
left: 5,
});
export default () => {
const [color, setColor] = React.useState('blue');
return (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledRadioGroup value={color} onValueChange={setColor}>
<StyledRadioItem value="red">
<StyledItemIndicator>
<CheckIcon />
</StyledItemIndicator>
Red
</StyledRadioItem>
<StyledRadioItem value="blue">
<StyledItemIndicator>
<CheckIcon />
</StyledItemIndicator>
Blue
</StyledRadioItem>
<StyledRadioItem value="green">
<StyledItemIndicator>
<CheckIcon />
</StyledItemIndicator>
Green
</StyledRadioItem>
</StyledRadioGroup>
</StyledContent>
</ContextMenu.Root>
);
};
Right click anywhere
import { styled } from 'path-to/stitches.config';
import * as ContextMenu from '@radix-ui/react-context-menu';
const StyledContent = styled(ContextMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(ContextMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const Image = styled('img', {
width: 24,
height: 24,
borderRadius: 9999,
marginRight: 10,
});
export default () => (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div style={{ backgroundColor: 'gainsboro', padding: 50, textAlign: 'center', }} >
Right click anywhere
</div>
</ContextMenu.Trigger>
<StyledContent>
<StyledItem onSelect={() => console.log('adolfo-hess')}>
<Image src="https://images.unsplash.com/photo-1463453091185-61582044d556?auto=format&fit=facearea&facepad=3&w=24&h=24&dpr=2&q=80" />
Adolfo Hess
</StyledItem>
<StyledItem onSelect={() => console.log('miyah-myles')}>
<Image src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=facearea&facepad=3&w=24&h=24&dpr=2&q=80" />
Miyah Myles
</StyledItem>
<StyledItem onSelect={() => console.log('sylvia-reynolds')}>
<Image src="https://images.unsplash.com/photo-1508186225823-0963cf9ab0de?auto=format&fit=facearea&facepad=3&w=24&h=24&dpr=2&q=80" />
Sylvia Reynolds
</StyledItem>
</StyledContent>
</ContextMenu.Root>
);

Uses roving tabindex to manage focus movement among menu items.

KeyDescription
SpaceActivates the focused item.
EnterActivates the focused item.
ArrowDownMoves focus to the next item.
ArrowUpMoves focus to the previous item.
EscCloses the context menu