XDSDropdownMenu@xds/core · DropdownMenu
Preview coming soon

Usage

A dropdown menu that displays a list of actionable items in a popup triggered by a button. Use to present action options as a next step in a process, or to offer contextual actions without cluttering the interface.

Best practices

GuidancePractices
DoKeep menu items concise and action-oriented so users can scan options quickly.
DoUse sections and dividers to group related actions when the menu has many items.
Don'tUse a DropdownMenu for navigation — use a navigation component instead.
Don'tPlace more than 10–12 items in a single menu without grouping them into sections.

Import

ts
import {XDSDropdownMenu} from '@xds/core/DropdownMenu'

Props

PropTypeDescription
itemsrequired
XDSDropdownMenuOption[]Menu items, dividers, or sections to display in the popup.
button
XDSDropdownMenuButtonProps (default: { label: 'Menu' })Props for the trigger button (XDSButton props except onClick).
isMenuOpen
booleanControlled open state for the menu.
onOpenChange
(isOpen: boolean) => voidCallback fired when the open state changes.
menuWidth
number | stringCustom menu width; defaults to matching the trigger button width.
onClick
() => voidCallback fired when the trigger button is clicked.
hasChevron
boolean (default: true)Whether to show a chevron icon on the trigger button. Set to false for icon-only triggers.
children
(item: XDSDropdownMenuItemData) => ReactNodeCustom render function for each item in the list.

Sub-components

DropdownMenu is a compound component with 5 sub-components.

XDSDropdownMenu

Main dropdown menu component with a trigger button and popup item list.
PropTypeDescription
itemsrequired
XDSDropdownMenuOption[]Menu items, dividers, or sections to display in the popup.
button
XDSDropdownMenuButtonProps (default: { label: 'Menu' })Props for the trigger button (XDSButton props except onClick).
isMenuOpen
booleanControlled open state for the menu.
onOpenChange
(isOpen: boolean) => voidCallback fired when the open state changes.
menuWidth
number | stringCustom menu width; defaults to matching the trigger button width.
onClick
() => voidCallback fired when the trigger button is clicked.
hasChevron
boolean (default: true)Whether to show a chevron icon on the trigger button. Set to false for icon-only triggers.
children
(item: XDSDropdownMenuItemData) => ReactNodeCustom render function for each item in the list.

XDSDropdownMenuDivider

A visual divider that can be placed between items in the `items` array.
PropTypeDescription
typerequired
'divider'Discriminant value that identifies this entry as a divider.

XDSDropdownMenuItem

Helper component for custom item rendering with consistent styling.
PropTypeDescription
icon
XDSIconTypeIcon to display before the label. See `npx xds docs icons` for valid semantic names.
label
ReactNodePrimary label text.
description
ReactNodeSecondary description text displayed below the label.
children
ReactNodeAdditional content rendered after the label and description.
xstyle
StyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}.

XDSDropdownMenuItemData

Data shape for a single actionable menu item passed via the `items` prop.
PropTypeDescription
labelrequired
stringDisplay label for the item.
onClick
() => voidCallback fired when the item is selected.
isDisabled
boolean (default: false)Whether the item is disabled; disabled items are skipped during keyboard navigation.
icon
XDSIconTypeIcon to display before the item label. See `npx xds docs icons` for valid semantic names.

XDSDropdownMenuSection

A labeled group of items that can be placed in the `items` array.
PropTypeDescription
typerequired
'section'Discriminant value that identifies this entry as a section.
itemsrequired
XDSDropdownMenuItemData[]The actionable items that belong to this section.
title
stringOptional header text displayed above the section items.

Examples

Common configurations, variations, and states.
DropdownMenu — ActionsAction menu with dividers separating safe and destructive operations. Use for row-level actions on items like documents, projects, or records.
tsx
'use client';
import {useState} from 'react';
import {XDSDropdownMenu} from '@xds/core/DropdownMenu';
import {XDSVStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
export default function DropdownMenuActions() {
const [lastAction, setLastAction] = useState<string | null>(null);
return (
<XDSVStack gap={3}>
<XDSDropdownMenu
button={{label: 'Actions'}}
items={[
{label: 'Edit', onClick: () => setLastAction('Edit')},
{label: 'Duplicate', onClick: () => setLastAction('Duplicate')},
{label: 'Move to folder', onClick: () => setLastAction('Move')},
{type: 'divider'},
{label: 'Archive', onClick: () => setLastAction('Archive')},
{label: 'Delete', onClick: () => setLastAction('Delete')},
]}
/>
{lastAction && (
<XDSText type="supporting" color="secondary">
Last action: {lastAction}
</XDSText>
)}
</XDSVStack>
);
}
DropdownMenu — DisabledMenu with selectively disabled items based on permissions. Use when some actions require higher privileges, like admin-only operations.
tsx
'use client';
import {useState} from 'react';
import {XDSDropdownMenu} from '@xds/core/DropdownMenu';
import {XDSVStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
export default function DropdownMenuWithDisabledItems() {
const [lastAction, setLastAction] = useState<string | null>(null);
return (
<XDSVStack gap={3}>
<XDSDropdownMenu
button={{label: 'Manage team'}}
items={[
{label: 'Invite member', onClick: () => setLastAction('Invite')},
{label: 'Edit roles', onClick: () => setLastAction('Edit roles')},
{type: 'divider'},
{label: 'Transfer ownership', isDisabled: true},
{label: 'Delete team', isDisabled: true},
]}
/>
{lastAction && (
<XDSText type="supporting" color="secondary">
Last action: {lastAction}
</XDSText>
)}
<XDSText type="supporting" color="secondary">
Destructive actions are disabled for non-admin users
</XDSText>
</XDSVStack>
);
}
DropdownMenu — Icon TriggerOverflow menu triggered by an icon-only button with no chevron or label text. Use for row-level actions in tables, cards, or lists where a text button would take too much space.
tsx
'use client';
import {useState} from 'react';
import {XDSDropdownMenu} from '@xds/core/DropdownMenu';
import {XDSIcon} from '@xds/core/Icon';
import {XDSVStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
import {EllipsisHorizontalIcon} from '@heroicons/react/24/outline';
export default function DropdownMenuNoChevron() {
const [lastAction, setLastAction] = useState<string | null>(null);
return (
<XDSVStack gap={3}>
<XDSDropdownMenu
button={{
label: 'More actions',
icon: <XDSIcon icon={EllipsisHorizontalIcon} />,
variant: 'ghost',
isIconOnly: true,
}}
hasChevron={false}
items={[
{label: 'Copy link', onClick: () => setLastAction('Copy link')},
{label: 'Download', onClick: () => setLastAction('Download')},
{label: 'Print', onClick: () => setLastAction('Print')},
{type: 'divider'},
{label: 'Report', onClick: () => setLastAction('Report')},
]}
/>
{lastAction && (
<XDSText type="supporting" color="secondary">
Last action: {lastAction}
</XDSText>
)}
</XDSVStack>
);
}
DropdownMenu — SectionsMenu items organized into titled sections for easy scanning. Use when you have 6+ actions that fall into distinct categories, like Create vs Manage.
tsx
'use client';
import {useState} from 'react';
import {XDSDropdownMenu} from '@xds/core/DropdownMenu';
import {XDSVStack} from '@xds/core/Layout';
import {XDSText} from '@xds/core/Text';
export default function DropdownMenuWithSections() {
const [lastAction, setLastAction] = useState<string | null>(null);
return (
<XDSVStack gap={3}>
<XDSDropdownMenu
button={{label: 'File', variant: 'ghost'}}
items={[
{
type: 'section',
title: 'Create',
items: [
{
label: 'New document',
onClick: () => setLastAction('New document'),
},
{
label: 'New spreadsheet',
onClick: () => setLastAction('New spreadsheet'),
},
{label: 'New folder', onClick: () => setLastAction('New folder')},
],
},
{
type: 'section',
title: 'Manage',
items: [
{label: 'Share', onClick: () => setLastAction('Share')},
{label: 'Move', onClick: () => setLastAction('Move')},
{label: 'Archive', onClick: () => setLastAction('Archive')},
],
},
]}
/>
{lastAction && (
<XDSText type="supporting" color="secondary">
Selected: {lastAction}
</XDSText>
)}
</XDSVStack>
);
}

Showcase source

tsx
'use client';
import {useState} from 'react';
import {XDSDropdownMenu} from '@xds/core/DropdownMenu';
export default function DropdownMenuShowcase() {
const [isMenuOpen, setIsMenuOpen] = useState(true);
return (
<XDSDropdownMenu
isMenuOpen={isMenuOpen}
onOpenChange={setIsMenuOpen}
button={{label: 'Actions'}}
items={[
{label: 'Edit', onClick: () => {}},
{label: 'Duplicate', onClick: () => {}},
{label: 'Delete', onClick: () => {}},
]}
/>
);
}