DropdownMenu

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;

Features

  • Can be generated from an array of item config objects.
  • Can be controlled or uncontrolled.
  • Supports submenus with configurable reading direction.
  • Supports items, labels, separators.
  • Supports modal and non-modal modes.
  • Customize side, alignment, offsets, collision handling.
  • Focus is fully managed.
  • Full keyboard navigation.
  • Typeahead support.
  • Secondary action support when holidng Shift key
  • Triggers with a long-press on touch devices

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.

PropTypeDefault
defaultOpenbooleanNo default value
openbooleanNo default value
onOpenChangefunctionNo default value
modalbooleantrue
direnum"ltr"
idstringNo default value

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

PropTypeDefault
asChildbooleantrue

The component that pops out in an open dropdown menu.

PropTypeDefault
childrennodeNo default value
contentarrayNo default value
allowPinchZoombooleanfalse
loopbooleanfalse
onCloseAutoFocusfunctionNo default value
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
onFocusOutsidefunctionNo default value
onInteractOutsidefunctionNo default value
portalledbooleantrue
forceMountbooleanNo default value
sideenum"bottom"
sideOffsetnumber0
alignenum"center"
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionTolerancenumber0

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.

PropTypeDefault
label*nodeNo default value
onSelect*functionNo default value
iconIconName | ReactElementNo default value
keyboardShortcutstring[]No default value
secondaryLabelnodeNo default value
secondaryOnSelectfunctionNo default value
secondaryIconIconName | ReactElementNo default value
secondaryKeyboardShortcutstring[]No default value
tooltipTitlenodeNo default value
tooltipDescriptionnodeNo default value
disabledbooleanNo default value
textValuestringNo default value

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.

PropTypeDefault
label*nodeNo default value
onSelectfunctionNo default value
iconIconName | ReactElementNo default value
keyboardShortcutstring[]No default value
tooltipTitlenodeNo default value
tooltipDescriptionnodeNo default value
disabledbooleanNo default value
textValuestringNo default value

A menu item designed for selecting a topic. Can be expanded to reveal subtopics.

PropTypeDefault
label*stringNo default value
onSelectfunctionNo default value
childrenReact.ComponentType<DropdownMenuTopicSelectionItemProps>No default value

A menu item designed for selecting a ContentColor.

PropTypeDefault
color*ContentColorNo default value
onSelectfunctionNo default value

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

PropTypeDefault
children*nodeNo default value

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',
},
// ...
];
PropTypeDefault
type*menu-labelNo default value
label*stringNo default value

To create a horizontal separator, add a MenuSeparatorConfig object to the menu array.

const menu = [
// ...
{
type: 'menu-separator',
},
// ...
];
PropTypeDefault
type*menu-separatorNo default value

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',
},
// ...
];
PropTypeDefault
type*menu-itemNo default value
label*nodeNo default value
iconIconName | ReactElementNo default value
onSelect*functionNo default value
keyboardShortcutstring[]No default value
secondaryLabelnodeNo default value
secondaryOnSelectfunctionNo default value
secondaryIconIconName | ReactElementNo default value
secondaryKeyboardShortcutstring[]No default value
tooltipTitlenodeNo default value
tooltipDescriptionnodeNo default value
submenuarrayNo default value
disabledbooleanNo default value

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: [],
},
],
},
// ...
];
PropTypeDefault
type*menu-topic-selection-itemNo default value
title*stringNo default value
id*stringNo default value
subtopics*TopicSelectionItem[]No default value
onSelectfunctionNo default value

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'),
},
// ...
];
PropTypeDefault
type*menu-color-selection-itemNo default value
color*ContentColorNo default value
onSelectfunctionNo default value

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.

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