Displays a menu to the user—such as a set of actions or functions—triggered by a button.
import React from 'react';
import { styled, keyframes } from '@stitches/react';
import { violet, mauve, blackA } from '@radix-ui/colors';
import {
HamburgerMenuIcon,
DotFilledIcon,
CheckIcon,
ChevronRightIcon,
} from '@radix-ui/react-icons';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
const slideUpAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideRightAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
});
const slideDownAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideLeftAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
});
const StyledContent = styled(DropdownMenuPrimitive.Content, {
minWidth: 220,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
animationFillMode: 'forwards',
willChange: 'transform, opacity',
'&[data-state="open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
},
},
});
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(DropdownMenuPrimitive.Item, { ...itemStyles });
const StyledCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, { ...itemStyles });
const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, { ...itemStyles });
const StyledTriggerItem = styled(DropdownMenuPrimitive.TriggerItem, {
'&[data-state="open"]': {
backgroundColor: violet.violet4,
color: violet.violet11,
},
...itemStyles,
});
const StyledLabel = styled(DropdownMenuPrimitive.Label, {
paddingLeft: 25,
fontSize: 12,
lineHeight: '25px',
color: mauve.mauve11,
});
const StyledSeparator = styled(DropdownMenuPrimitive.Separator, {
height: 1,
backgroundColor: violet.violet6,
margin: 5,
});
const StyledItemIndicator = styled(DropdownMenuPrimitive.ItemIndicator, {
position: 'absolute',
left: 0,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
});
const StyledArrow = styled(DropdownMenuPrimitive.Arrow, {
fill: 'white',
});
// Exports
export const DropdownMenu = DropdownMenuPrimitive.Root;
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
export const DropdownMenuContent = StyledContent;
export const DropdownMenuItem = StyledItem;
export const DropdownMenuCheckboxItem = StyledCheckboxItem;
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
export const DropdownMenuRadioItem = StyledRadioItem;
export const DropdownMenuItemIndicator = StyledItemIndicator;
export const DropdownMenuTriggerItem = StyledTriggerItem;
export const DropdownMenuLabel = StyledLabel;
export const DropdownMenuSeparator = StyledSeparator;
export const DropdownMenuArrow = StyledArrow;
// Your app...
const Box = styled('div', {});
const RightSlot = styled('div', {
marginLeft: 'auto',
paddingLeft: 20,
color: mauve.mauve11,
':focus > &': { color: 'white' },
'[data-disabled] &': { color: mauve.mauve8 },
});
const IconButton = styled('button', {
all: 'unset',
fontFamily: 'inherit',
borderRadius: '100%',
height: 35,
width: 35,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: violet.violet11,
backgroundColor: 'white',
boxShadow: `0 2px 10px ${blackA.blackA7}`,
'&:hover': { backgroundColor: violet.violet3 },
'&:focus': { boxShadow: `0 0 0 2px black` },
});
export const DropdownMenuDemo = () => {
const [bookmarksChecked, setBookmarksChecked] = React.useState(true);
const [urlsChecked, setUrlsChecked] = React.useState(false);
const [person, setPerson] = React.useState('pedro');
return (
<Box>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton aria-label="Customise options">
<HamburgerMenuIcon />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={5}>
<DropdownMenuItem>
New Tab <RightSlot>⌘+T</RightSlot>
</DropdownMenuItem>
<DropdownMenuItem>
New Window <RightSlot>⌘+N</RightSlot>
</DropdownMenuItem>
<DropdownMenuItem disabled>
New Private Window <RightSlot>⇧+⌘+N</RightSlot>
</DropdownMenuItem>
<DropdownMenu>
<DropdownMenuTriggerItem>
More Tools
<RightSlot>
<ChevronRightIcon />
</RightSlot>
</DropdownMenuTriggerItem>
<DropdownMenuContent sideOffset={2} alignOffset={-5}>
<DropdownMenuItem>
Save Page As… <RightSlot>⌘+S</RightSlot>
</DropdownMenuItem>
<DropdownMenuItem>Create Shortcut…</DropdownMenuItem>
<DropdownMenuItem>Name Window…</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Developer Tools</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
checked={bookmarksChecked}
onCheckedChange={setBookmarksChecked}
>
<DropdownMenuItemIndicator>
<CheckIcon />
</DropdownMenuItemIndicator>
Show Bookmarks <RightSlot>⌘+B</RightSlot>
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem checked={urlsChecked} onCheckedChange={setUrlsChecked}>
<DropdownMenuItemIndicator>
<CheckIcon />
</DropdownMenuItemIndicator>
Show Full URLs
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>People</DropdownMenuLabel>
<DropdownMenuRadioGroup value={person} onValueChange={setPerson}>
<DropdownMenuRadioItem value="pedro">
<DropdownMenuItemIndicator>
<DotFilledIcon />
</DropdownMenuItemIndicator>
Pedro Duarte
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="colm">
<DropdownMenuItemIndicator>
<DotFilledIcon />
</DropdownMenuItemIndicator>
Colm Tuite
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
<DropdownMenuArrow offset={12} />
</DropdownMenuContent>
</DropdownMenu>
</Box>
);
};
export default DropdownMenuDemo;
Install the component from your command line.
npm install @radix-ui/react-dropdown-menu
Import all parts and piece them together.
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger />
<DropdownMenu.Content>
<DropdownMenu.Label />
<DropdownMenu.Item />
<DropdownMenu.Group>
<DropdownMenu.Item />
</DropdownMenu.Group>
<DropdownMenu.CheckboxItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.CheckboxItem>
<DropdownMenu.RadioGroup>
<DropdownMenu.RadioItem>
<DropdownMenu.ItemIndicator />
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
<DropdownMenu.Root>
<DropdownMenu.TriggerItem />
<DropdownMenu.Content />
</DropdownMenu.Root>
<DropdownMenu.Separator />
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Root>
);
Contains all the parts of a dropdown menu.
Prop | Type | Default |
---|---|---|
defaultOpen | boolean | |
open | boolean | |
onOpenChange | function | |
modal | boolean | true |
dir | enum | "ltr" |
The button that toggles the dropdown menu. By default, the DropdownMenu.Content
will position itself against the trigger.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
The component that pops out when the dropdown menu is open.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
allowPinchZoom | boolean | false |
loop | boolean | false |
onCloseAutoFocus | function | |
onEscapeKeyDown | function | |
onPointerDownOutside | function | |
onFocusOutside | function | |
onInteractOutside | function | |
portalled | boolean | true |
forceMount | boolean | |
side | enum | "bottom" |
sideOffset | number | 0 |
align | enum | "center" |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionTolerance | number | 0 |
An optional arrow element to render alongside the dropdown menu. This can be used to help visually link the trigger with the DropdownMenu.Content
. Must be rendered inside DropdownMenu.Content
.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
width | number | 10 |
height | number | 5 |
offset | number |
The component that contains the dropdown 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 DropdownMenu
. Must be rendered inside DropdownMenu.Root
.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
disabled | boolean | |
textValue | string |
Used to group multiple DropdownMenu.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 DropdownMenu.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 DropdownMenu.CheckboxItem
or DropdownMenu.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 dropdown menu.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
You can create submenus by nesting DropdownMenu
s and using a TriggerItem
in place of a Trigger
.
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Root>
<DropdownMenu.TriggerItem>Sub menu →</DropdownMenu.TriggerItem>
<DropdownMenu.Content>
<DropdownMenu.Item>Sub menu item</DropdownMenu.Item>
<DropdownMenu.Item>Sub menu item</DropdownMenu.Item>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Root>
<DropdownMenu.Separator />
<DropdownMenu.Item>…</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
You can add special styles to disabled items via the data-disabled
attribute.
import { styled } from '@stitches/react';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const StyledItem = styled(DropdownMenu.Item, {
'&[data-disabled]': { color: 'gainsboro' },
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<StyledItem disabled>…</StyledItem>
<StyledItem>…</StyledItem>
<StyledItem>…</StyledItem>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
Use the Separator
part to add a separator between items.
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item>…</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Use the Label
part to help label a section.
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Label>Label</DropdownMenu.Label>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Item>…</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.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 DropdownMenu from '@radix-ui/react-dropdown-menu';
export default () => {
const [checked, setChecked] = React.useState(true);
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Item>…</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.CheckboxItem
checked={checked}
onCheckedChange={setChecked}
>
<DropdownMenu.ItemIndicator>
<CheckIcon />
</DropdownMenu.ItemIndicator>
Checkbox item
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.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 DropdownMenu from '@radix-ui/react-dropdown-menu';
export default () => {
const [color, setColor] = React.useState('blue');
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup value={color} onValueChange={setColor}>
<DropdownMenu.RadioItem value="red">
<DropdownMenu.ItemIndicator>
<CheckIcon />
</DropdownMenu.ItemIndicator>
Red
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="blue">
<DropdownMenu.ItemIndicator>
<CheckIcon />
</DropdownMenu.ItemIndicator>
Blue
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="green">
<DropdownMenu.ItemIndicator>
<CheckIcon />
</DropdownMenu.ItemIndicator>
Green
</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
};
import { styled } from '@stitches/react';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const Image = styled('img', {
width: 24,
height: 24,
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>
<Image src="…" />
Adolfo Hess
</DropdownMenu.Item>
<DropdownMenu.Item>
<Image src="…" />
Miyah Myles
</DropdownMenu.Item>
<DropdownMenu.Item>
<Image src="…" />
Sylvia Reynolds
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
);
We expose a CSS custom property --radix-dropdown-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 DropdownMenu from '@radix-ui/react-dropdown-menu';
const scaleIn = keyframes({
'0%': { opacity: 0, transform: 'scale(0)' },
'100%': { opacity: 1, transform: 'scale(1)' },
});
const StyledContent = styled(DropdownMenu.Content, {
transformOrigin: 'var(--radix-dropdown-menu-content-transform-origin)',
animation: `${scaleIn} 0.5s ease-out forwards`,
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<StyledContent>…</StyledContent>
</DropdownMenu.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 DropdownMenu from '@radix-ui/react-dropdown-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(DropdownMenu.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 () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>…</DropdownMenu.Trigger>
<StyledContent>…</StyledContent>
</DropdownMenu.Root>
);
Adheres to the Menu Button WAI-ARIA design pattern and uses roving tabindex to manage focus movement among menu items.
Key | Description |
---|---|
Space | When focus is on DropdownMenu.Trigger , opens the dropdown menu and focuses the first item.When focus is on an item, activates the focused item. |
Enter | When focus is on DropdownMenu.Trigger , opens the dropdown menu and focuses the first item.When focus is on an item, activates the focused item. |
ArrowDown | When focus is on DropdownMenu.Trigger , opens the dropdown menu.When focus is on an item, moves focus to the next item. |
ArrowUp | When focus is on an item, moves focus to the previous item. |
ArrowRightArrowLeft | When focus is on DropdownMenu.TriggerItem , opens or closes the submenu depending on reading direction. |
Esc | Closes the dropdown menu and moves focus to DropdownMenu.Trigger . |
Create your own API by abstracting the primitive parts into your own component.
This example abstracts the DropdownMenu.Arrow
and DropdownMenu.ItemIndicator
parts.
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuGroup,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
} from './your-dropdown-menu';
export default () => (
<DropdownMenu>
<DropdownMenuTrigger>DropdownMenu trigger</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item</DropdownMenuItem>
<DropdownMenuLabel>Label</DropdownMenuLabel>
<DropdownMenuGroup>Group</DropdownMenuGroup>
<DropdownMenuCheckboxItem>CheckboxItem</DropdownMenuCheckboxItem>
<DropdownMenuSeparator>Separator</DropdownMenuSeparator>
<DropdownMenuRadioGroup>
<DropdownMenuRadioItem>RadioItem</DropdownMenuRadioItem>
<DropdownMenuRadioItem>RadioItem</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
// your-dropdown-menu.js
import React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { CheckIcon } from '@radix-ui/react-icons';
export const DropdownMenu = DropdownMenuPrimitive.Root;
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
export const DropdownMenuContent = React.forwardRef(
({ children, ...props }, forwardedRef) => {
return (
<DropdownMenuPrimitive.Content ref={forwardedRef}>
{children}
<DropdownMenuPrimitive.Arrow />
</DropdownMenuPrimitive.Content>
);
}
);
export const DropdownMenuLabel = DropdownMenuPrimitive.Label;
export const DropdownMenuItem = DropdownMenuPrimitive.Item;
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
export const DropdownMenuCheckboxItem = React.forwardRef(
({ children, ...props }, forwardedRef) => {
return (
<DropdownMenuPrimitive.CheckboxItem {...props} ref={forwardedRef}>
{children}
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon />
</DropdownMenuPrimitive.ItemIndicator>
</DropdownMenuPrimitive.CheckboxItem>
);
}
);
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
export const DropdownMenuRadioItem = React.forwardRef(
({ children, ...props }, forwardedRef) => {
return (
<DropdownMenuPrimitive.RadioItem {...props} ref={forwardedRef}>
{children}
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon />
</DropdownMenuPrimitive.ItemIndicator>
</DropdownMenuPrimitive.RadioItem>
);
}
);
export const DropdownMenuSeparator = DropdownMenuPrimitive.Separator;