Dropdown Menu

Displays a menu to the user—such as a set of actions or functions—triggered by a button.

Features

  • Can be controlled or uncontrolled.
  • Supports items, labels, groups of items.
  • Supports checkable items (single or multiple).
  • Customize side, alignment, offsets, collision handling.
  • Optionally render a pointing arrow.
  • 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-dropdown-menu

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

Create your styled dropdown menu component from the primitive parts.

import { styled } from 'path-to/stitches.config';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const StyledContent = styled(DropdownMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(DropdownMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const StyledArrow = styled(DropdownMenu.Arrow, {
fill: 'white',
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>Trigger</DropdownMenu.Trigger>
<StyledContent>
<StyledItem onSelect={() => console.log('cut')}>Cut</StyledItem>
<StyledItem onSelect={() => console.log('copy')}>
Copy
</StyledItem>
<StyledItem onSelect={() => console.log('paste')}>
Paste
</StyledItem>
<StyledArrow />
</StyledContent>
</DropdownMenu.Root>
);

Contains all the parts of a dropdown menu.

PropTypeDefault
defaultOpenbooleanNo default value
openbooleanNo default value
onOpenChangefunctionNo default value
idstringNo default value

The button that toggles the dropdown menu. By default, the DropdownMenu.Content will position itself against the trigger.

PropTypeDefault
asenumbutton

The component that pops out when the dropdown menu is open.

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

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.

PropTypeDefault
asenumsvg
widthnumber10
heightnumber5
offsetnumberNo default value

The component that contains the dropdown menu items.

PropTypeDefault
asenumdiv
disabledbooleanNo default value
onSelectfunctionNo default value
textValuestringNo default value

Used to group multiple DropdownMenu.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 DropdownMenu.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 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.

PropTypeDefault
asenumspan
forceMountbooleanNo default value

Used to visually separate items in the dropdown menu.

PropTypeDefault
asenumdiv

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.

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

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(DropdownMenu.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.

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

Use the Separator part to add a separator between items.

import { styled } from 'path-to/stitches.config';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const StyledContent = styled(DropdownMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(DropdownMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const StyledSeparator = styled(DropdownMenu.Separator, {
height: 1,
backgroundColor: 'gainsboro',
margin: 5,
});
const StyledArrow = styled(DropdownMenu.Arrow, {
fill: 'white',
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>Trigger</DropdownMenu.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>
</DropdownMenu.Root>
);

Use the Label part to help label a section.

import { styled } from 'path-to/stitches.config';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const StyledContent = styled(DropdownMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(DropdownMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const StyledLabel = styled(DropdownMenu.Label, {
color: 'slategray',
fontSize: 13,
padding: '5px 10px',
cursor: 'default',
});
const StyledSeparator = styled(DropdownMenu.Separator, {
height: 1,
backgroundColor: 'gainsboro',
margin: 5,
});
const StyledArrow = styled(DropdownMenu.Arrow, {
fill: 'white',
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>Trigger</DropdownMenu.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>
<StyledArrow />
</StyledContent>
</DropdownMenu.Root>
);

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

import { styled } from 'path-to/stitches.config';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { CheckIcon } from '@radix-ui/react-icons';
const StyledContent = styled(DropdownMenu.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(DropdownMenu.Item, itemStyles);
const StyledCheckboxItem = styled(
DropdownMenu.CheckboxItem,
itemStyles
);
const StyledItemIndicator = styled(DropdownMenu.ItemIndicator, {
position: 'absolute',
left: 5,
});
const StyledSeparator = styled(DropdownMenu.Separator, {
height: 1,
backgroundColor: 'gainsboro',
margin: 5,
});
const StyledArrow = styled(DropdownMenu.Arrow, {
fill: 'white',
});
export default () => {
const [checked, setChecked] = React.useState(true);
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>Trigger</DropdownMenu.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>
<StyledArrow />
</StyledContent>
</DropdownMenu.Root>
);
};

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

import { styled } from 'path-to/stitches.config';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { CheckIcon } from '@radix-ui/react-icons';
const StyledContent = styled(DropdownMenu.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(DropdownMenu.Item, itemStyles);
const StyledRadioGroup = styled(DropdownMenu.RadioGroup, {});
const StyledRadioItem = styled(DropdownMenu.RadioItem, itemStyles);
const StyledItemIndicator = styled(DropdownMenu.ItemIndicator, {
position: 'absolute',
left: 5,
});
const StyledArrow = styled(DropdownMenu.Arrow, {
fill: 'white',
});
export default () => {
const [color, setColor] = React.useState('blue');
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>Trigger</DropdownMenu.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>
<StyledArrow />
</StyledContent>
</DropdownMenu.Root>
);
};
import { styled } from 'path-to/stitches.config';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
const StyledContent = styled(DropdownMenu.Content, {
minWidth: 130,
backgroundColor: 'white',
borderRadius: 6,
padding: 5,
boxShadow: '0px 5px 15px -5px hsla(206,22%,7%,.15)',
});
const StyledItem = styled(DropdownMenu.Item, {
fontSize: 13,
padding: '5px 10px',
borderRadius: 3,
cursor: 'default',
'&:focus': {
outline: 'none',
backgroundColor: 'dodgerblue',
color: 'white',
},
});
const StyledArrow = styled(DropdownMenu.Arrow, {
fill: 'white',
});
const Image = styled('img', {
width: 24,
height: 24,
borderRadius: 9999,
marginRight: 10,
});
export default () => (
<DropdownMenu.Root>
<DropdownMenu.Trigger>Trigger</DropdownMenu.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>
<StyledArrow />
</StyledContent>
</DropdownMenu.Root>
);

Adheres to the Menu Button WAI-ARIA design pattern and uses roving tabindex to manage focus movement among menu items.

KeyDescription
SpaceWhen focus is on DropdownMenu.Trigger, opens the dropdown menu.
When focus is on an item, activates the focused item.
EnterWhen focus is on DropdownMenu.Trigger, opens the dropdown menu.
When focus is on an item, activates the focused item.
ArrowDownWhen focus is on DropdownMenu.Trigger, opens the dropdown menu.
When focus is on an item, moves focus to the next item.
ArrowUpWhen focus is on DropdownMenu.Trigger, opens the dropdown menu.
When focus is on an item, moves focus to the previous item.
EscCloses 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.

Usage

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>
<DropdownMenuRadioGroup>RadioGroup</DropdownMenuRadioGroup>
<DropdownMenuRadioItem>RadioItem</DropdownMenuRadioItem>
<DropdownMenuSeparator>Separator</DropdownMenuSeparator>
</DropdownMenuContent>
</DropdownMenu>
);

Implementation

// 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;