Displays a menu to the user—such as a set of actions or functions—triggered by a button.
/* eslint-disable no-console */
import React from 'react';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
IconButton,
ContentColors,
} from '@minddrop/ui';
export const DropdownMenuDemo = () => (
<div style={{ display: 'flex', alignItems: 'center' }}>
<DropdownMenu>
<DropdownMenuTrigger>
<IconButton
icon="more-vertical"
label="Drop options"
color="contrast"
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
style={{ width: 240 }}
content={[
{
type: 'menu-item',
label: 'Add title',
icon: 'title',
onSelect: () => console.log('Add title'),
keyboardShortcut: ['Ctrl', 'T'],
},
{
type: 'menu-item',
label: 'Add note',
icon: 'note',
onSelect: () => console.log('Add note'),
keyboardShortcut: ['Ctrl', 'Shift', 'N'],
},
{
type: 'menu-item',
label: 'Color',
icon: 'color-palette',
submenuContentClass: 'color-selection-submenu',
submenu: ContentColors.map((color) => ({
type: 'menu-color-selection-item',
color: color.value,
onSelect: () => console.log(color.value),
})),
},
{
type: 'menu-item',
label: 'Turn into',
icon: 'turn-into',
submenu: [
{
type: 'menu-item',
label: 'Text',
onSelect: () => console.log('Turn into text'),
},
{
type: 'menu-item',
label: 'Image',
onSelect: () => console.log('Turn into image'),
},
{
type: 'menu-item',
label: 'Equation',
onSelect: () => console.log('Turn into equation'),
},
],
},
{
type: 'menu-separator',
},
{
type: 'menu-label',
label: 'Actions',
},
{
type: 'menu-item',
label: 'Copy link',
icon: 'link',
onSelect: () => console.log('Copy link'),
keyboardShortcut: ['Ctrl', 'Shift', 'C'],
tooltipTitle: 'Copy drop link',
tooltipDescription:
'Paste the link into other drops to create a network or related information.',
},
{
type: 'menu-item',
label: 'Move to',
icon: 'arrow-up-right',
onSelect: () => console.log('Move to'),
submenuContentClass: 'topic-selection-submenu',
submenu: [
{
type: 'menu-topic-selection-item',
label: 'Sailing',
onSelect: () => console.log("Move to 'Sailing'"),
subtopics: [
{
type: 'menu-topic-selection-item',
label: 'Navigation',
onSelect: () => console.log("Move to 'Navigation'"),
subtopics: [
{
type: 'menu-topic-selection-item',
label: 'Coastal navigation',
onSelect: () =>
console.log("Move to 'Coastal navigation'"),
subtopics: [],
},
{
type: 'menu-topic-selection-item',
label: 'Offshore navigation',
onSelect: () =>
console.log("Move to 'Offshore navigation'"),
subtopics: [],
},
],
},
{
type: 'menu-topic-selection-item',
id: 'anchoring',
label: 'Anchoring',
onSelect: () => console.log("Move to 'Anchoring'"),
subtopics: [],
},
{
type: 'menu-topic-selection-item',
label: 'Sailboats',
onSelect: () => console.log("Move to 'Sailboats'"),
subtopics: [],
},
],
},
{
type: 'menu-topic-selection-item',
label: 'Home',
onSelect: () => console.log("Move to 'Home'"),
subtopics: [],
},
{
type: 'menu-topic-selection-item',
label: 'Tea',
onSelect: () => console.log("Move to 'Tea'"),
subtopics: [],
},
{
type: 'menu-topic-selection-item',
label: 'Work',
onSelect: () => console.log("Move to 'work'"),
subtopics: [],
},
{
type: 'menu-topic-selection-item',
label: 'Japanese',
onSelect: () => console.log("Move to 'Japanese'"),
subtopics: [],
},
],
},
{
type: 'menu-item',
label: 'Delete',
icon: 'trash',
onSelect: () => console.log('Delete'),
keyboardShortcut: ['Del'],
secondaryLabel: 'Delete everywhere',
secondaryOnSelect: () => console.log('Delete everywhere'),
tooltipTitle: 'Delete drop',
tooltipDescription: 'Shift + Click to delete from all topics',
},
]}
/>
</DropdownMenu>
</div>
);
export default DropdownMenuDemo;
You can compose a menu using the available components.
import {
IconButton,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuItem,
DropdownMenuTriggerItem,
DropdownMenuTopicSelectionItem,
DropdownMenuColorSelectionItem,
ContentColors,
} from '@minddrop/ui';
export default () => {
return (
<DropdownMenu>
<DropdownMenuTrigger>
<IconButton icon="more-vertical" label="Drop options" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
label="Add title"
icon="title"
keyboardShortcut={['Ctrl', 'T']}
onSelect={() => console.log('Add title')}
/>
<DropdownMenuItem
label="Add note"
icon="note"
keyboardShortcut={['Ctrl', 'Shift', 'N']}
onSelect={() => console.log('Add note')}
/>
<DropdownMenu>
<DropdownMenuTriggerItem
label="Color"
icon="color-palette"
/>
<DropdownMenuContent className="color-selection-submenu">
{ContentColors.map((color) => (
<DropdownMenuColorSelectionItem
key={color.value}
color={color.value}
onSelect={() => console.log(color.value)}
/>
))}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTriggerItem
label="Turn into"
icon="turn-into"
/>
<DropdownMenuContent>
<DropdownMenuItem
label="Text"
onSelect={() => console.log('Turn into text')}
/>
<DropdownMenuItem
label="Image"
onSelect={() => console.log('Turn into image')}
/>
<DropdownMenuItem
label="Equation"
onSelect={() => console.log('Turn into equation')}
/>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuSeparator />
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
label="Copy link"
icon="link"
keyboardShortcut={['Ctrl', 'Shift', 'C']}
onSelect={() => console.log('Copy link')}
tooltipTitle="Copy drop link"
tooltipDescription="Paste the link into other drops to create a network or related information."
/>
<DropdownMenu>
<DropdownMenuTriggerItem
label="Move to"
icon="arrow-up-right"
/>
<DropdownMenuContent className="topic-selection-submenu">
<DropdownMenuTopicSelectionItem
label="Sailing"
onSelect={() => console.log("Move to 'Sailing'")}
>
<DropdownMenuTopicSelectionItem
label="Navigation"
onSelect={() => console.log("Move to 'Navigation'")}
>
// ...
</DropdownMenuTopicSelectionItem>
// ...
</DropdownMenuTopicSelectionItem>
// ...
</DropdownMenuContent>
</DropdownMenu>
<ContextMenuItem
label="Delete"
icon="trash"
keyboardShortcut={['Del']}
onSelect={() => console.log('Delete')}
tooltipTitle="Delete drop"
tooltipDescription="Shift + Click to delete from all topics"
/>
</DropdownMenuContent>
</DropdownMenu>
);
};
Dropdown menus can also be generated by passing an array of item configs as the DropdownMenuContent
's content
prop. The generated menu below is equivalent to the composed example above.
See the menu component configs section for details on the different configuration objects.
import {
IconButton,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
MenuContent,
ContentColors,
} from '@minddrop/ui';
const menu: MenuContent = [
{
type: 'menu-item',
label: 'Add title',
icon: 'title',
onSelect: () => console.log('Add title'),
keyboardShortcut: ['Ctrl', 'T'],
},
{
type: 'menu-item',
label: 'Add note',
icon: 'note',
onSelect: () => console.log('Add note'),
keyboardShortcut: ['Ctrl', 'Shift', 'N'],
},
{
type: 'menu-item',
label: 'Color',
icon: 'color-palette',
submenuContentClass: 'color-selection-submenu',
submenu: ContentColors.map((color) => ({
type: 'menu-color-selection-item',
color: color.value,
onSelect: () => console.log(color.value),
})),
},
{
type: 'menu-item',
label: 'Turn into',
icon: 'turn-into',
submenu: [
{
type: 'menu-item',
label: 'Text',
onSelect: () => console.log('Turn into text'),
},
{
type: 'menu-item',
label: 'Image',
onSelect: () => console.log('Turn into image'),
},
{
type: 'menu-item',
label: 'Equation',
onSelect: () => console.log('Turn into equation'),
},
],
},
{
type: 'menu-separator',
},
{
type: 'menu-label',
label: 'Actions',
},
{
type: 'menu-item',
label: 'Copy link',
icon: 'link',
onSelect: () => console.log('Copy link'),
keyboardShortcut: ['Ctrl', 'Shift', 'C'],
tooltipTitle: 'Copy drop link',
tooltipDescription:
'Paste the link into other drops to create a network or related information.',
},
{
type: 'menu-item',
label: 'Move to',
icon: 'arrow-up-right',
submenuContentClass: 'topic-selection-submenu',
onSelect: () => console.log('Move to'),
submenu: [
{
id: 'sailing',
label: 'Sailing',
onSelect: () => console.log("Move to 'Sailing'"),
subtopics: [
{
id: 'navigation',
label: 'Navigation',
onSelect: () => console.log("Move to 'Navigation'"),
subtopics: [
// ...
],
},
// ...
],
},
// ...
],
},
{
type: 'menu-item',
label: 'Delete',
icon: 'trash',
onSelect: () => console.log('Delete'),
keyboardShortcut: ['Del'],
secondaryLabel: 'Delete everywhere',
secondaryOnSelect: () => console.log('Delete everywhere'),
tooltipTitle: 'Delete drop',
tooltipDescription: 'Shift + Click to delete from all topics',
},
];
export default () => {
return (
<DropdownMenu>
<DropdownMenuTrigger>
<IconButton icon="more-vertical" label="Drop options" />
</DropdownMenuTrigger>
<DropdownMenuContent content={menu} />
</DropdownMenu>
);
};
Contains all the parts of a dropdown menu.
Prop | Type | Default |
---|---|---|
defaultOpen | boolean | |
open | boolean | |
onOpenChange | function | |
modal | boolean | true |
dir | enum | "ltr" |
id | string |
The area that opens the dropdown menu. Wrap it around the target you want the dropdown menu to open from when right-clicking (or using the relevant keyboard shortcuts).
Prop | Type | Default |
---|---|---|
asChild | boolean | true |
The component that pops out in an open dropdown menu.
Prop | Type | Default |
---|---|---|
children | node | |
content | array | |
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 item in the menu. Items can be selected using the mouse or keyboard. Items can be given a secondary action which is enabled when the Shift key is held down.
Prop | Type | Default |
---|---|---|
label* | node | |
onSelect* | function | |
icon | IconName | ReactElement | |
keyboardShortcut | string[] | |
secondaryLabel | node | |
secondaryOnSelect | function | |
secondaryIcon | IconName | ReactElement | |
secondaryKeyboardShortcut | string[] | |
tooltipTitle | node | |
tooltipDescription | node | |
disabled | boolean | |
textValue | string |
An item that opens a submenu. Used in combination with a nested DropdownMenu
. Must be rendered inside a root DropdownMenu
. Has the same props API as a regular DropdownMenuItem
.
Prop | Type | Default |
---|---|---|
label* | node | |
onSelect | function | |
icon | IconName | ReactElement | |
keyboardShortcut | string[] | |
tooltipTitle | node | |
tooltipDescription | node | |
disabled | boolean | |
textValue | string |
A menu item designed for selecting a topic. Can be expanded to reveal subtopics.
Prop | Type | Default |
---|---|---|
label* | string | |
onSelect | function | |
children | React.ComponentType<DropdownMenuTopicSelectionItemProps> |
A menu item designed for selecting a ContentColor
.
Prop | Type | Default |
---|---|---|
color* | ContentColor | |
onSelect | function |
Used to render a label. It won't be focusable using arrow keys.
Prop | Type | Default |
---|---|---|
children* | node |
Used to visually separate items in the dropdown menu. Does not take any props.
Dropdown menus can also be generated by passing an array of menu component configuration objects and React components as the DropdownMenuContent
's content
prop.
To create a menu label, add a MenuLabelConfig
config object to the menu array.
const menu = [
// ...
{
type: 'menu-label',
label: 'Actions',
},
// ...
];
Prop | Type | Default |
---|---|---|
type* | menu-label | |
label* | string |
To create a horizontal separator, add a MenuSeparatorConfig
object to the menu array.
const menu = [
// ...
{
type: 'menu-separator',
},
// ...
];
Prop | Type | Default |
---|---|---|
type* | menu-separator |
To create a menu item, add a MenuItemConfig
object to the menu array.
const menu = [
// ...
{
type: 'menu-item',
label: 'Copy link',
icon: 'link',
onSelect: () => console.log('Copy link'),
keyboardShortcut: ['Ctrl', 'Shift', 'C'],
tooltipTitle: 'Copy drop link',
tooltipDescription:
'Paste the link into other drops to create a network or related information.',
},
// With submenu
{
type: 'menu-item',
label: 'Turn into',
icon: 'turn-into',
submenu: [
{
type: 'menu-item',
label: 'Text',
onSelect: () => console.log('Turn into text'),
},
{
type: 'menu-item',
label: 'Image',
onSelect: () => console.log('Turn into image'),
},
{
type: 'menu-item',
label: 'Equation',
onSelect: () => console.log('Turn into equation'),
},
],
},
// With secondary action
{
type: 'menu-item',
label: 'Delete',
icon: 'trash',
onSelect: () => console.log('Delete'),
keyboardShortcut: ['Del'],
secondaryLabel: 'Delete everywhere',
secondaryOnSelect: () => console.log('Delete everywhere'),
tooltipTitle: 'Delete drop',
tooltipDescription: 'Shift + Click to delete from all topics',
},
// ...
];
Prop | Type | Default |
---|---|---|
type* | menu-item | |
label* | node | |
icon | IconName | ReactElement | |
onSelect* | function | |
keyboardShortcut | string[] | |
secondaryLabel | node | |
secondaryOnSelect | function | |
secondaryIcon | IconName | ReactElement | |
secondaryKeyboardShortcut | string[] | |
tooltipTitle | node | |
tooltipDescription | node | |
submenu | array | |
disabled | boolean |
To create a topic selection menu item, add a MenuTopicSelectionItemConfig
object to the menu array.
const menu = [
// ...
{
type: 'menu-topic-selection-item',
label: 'Sailing',
onSelect: () => console.log("Move to 'Sailing'"),
subtopics: [
{
type: 'menu-topic-selection-item',
label: 'Sailing',
onSelect: () => console.log("Move to 'Sailing'"),
subtopics: [],
},
],
},
// ...
];
Prop | Type | Default |
---|---|---|
type* | menu-topic-selection-item | |
title* | string | |
id* | string | |
subtopics* | TopicSelectionItem[] | |
onSelect | function |
To create a color selection menu item, add a MenuColorSelectionItemConfig
object to the menu array.
const menu = [
// ...
{
type: 'menu-color-selection-item',
color: 'green',
onSelect: () => console.log('green'),
},
// ...
];
Prop | Type | Default |
---|---|---|
type* | menu-color-selection-item | |
color* | ContentColor | |
onSelect | function |
Radix exposes 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.
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
.dropdown-menu {
transform-origin: var(
--radix-dropdown-menu-content-transform-origin
);
animation: scaleIn 0.5s ease-out;
}
Radix exposes data-side
and data-align
attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(0);
}
to {
opacity: 1;
transform: translateY(-10px);
}
}
.dropdown-menu {
animation-duration: 0.6s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.dropdown-menu[data-side='bottom'] {
animation: slideDown;
}
.dropdown-menu[data-side='top'] {
animation: slideUp;
}
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 DropdownMenuTrigger , opens the dropdown menu and focuses the first item.When focus is on an item, activates the focused item. |
ArrowDown | When focus is on DropdownMenuTrigger , 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 DropdownMenuTriggerItem , opens or closes the submenu depending on reading direction. When focus is on DropdownMenuTopicSelectionItem , expands or collapses the topic's subtopics. |
Esc | Closes the dropdown menu and moves focus to DropdownMenuTrigger . |