XDSDropdownMenu@xds/core · DropdownMenu
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
| Guidance | Practices |
|---|---|
| Do | Keep menu items concise and action-oriented so users can scan options quickly. |
| Do | Use sections and dividers to group related actions when the menu has many items. |
| Don't | Use a DropdownMenu for navigation — use a navigation component instead. |
| Don't | Place more than 10–12 items in a single menu without grouping them into sections. |
Import
tsimport {XDSDropdownMenu} from '@xds/core/DropdownMenu'
Props
| Prop | Type | Description |
|---|---|---|
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 | boolean | Controlled open state for the menu. |
onOpenChange | (isOpen: boolean) => void | Callback fired when the open state changes. |
menuWidth | number | string | Custom menu width; defaults to matching the trigger button width. |
onClick | () => void | Callback 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) => ReactNode | Custom 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.| Prop | Type | Description |
|---|---|---|
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 | boolean | Controlled open state for the menu. |
onOpenChange | (isOpen: boolean) => void | Callback fired when the open state changes. |
menuWidth | number | string | Custom menu width; defaults to matching the trigger button width. |
onClick | () => void | Callback 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) => ReactNode | Custom render function for each item in the list. |
XDSDropdownMenuDivider
A visual divider that can be placed between items in the `items` array.| Prop | Type | Description |
|---|---|---|
typerequired | 'divider' | Discriminant value that identifies this entry as a divider. |
XDSDropdownMenuItem
Helper component for custom item rendering with consistent styling.| Prop | Type | Description |
|---|---|---|
icon | XDSIconType | Icon to display before the label. See `npx xds docs icons` for valid semantic names. |
label | ReactNode | Primary label text. |
description | ReactNode | Secondary description text displayed below the label. |
children | ReactNode | Additional content rendered after the label and description. |
xstyle | StyleXStyles | StyleX 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.| Prop | Type | Description |
|---|---|---|
labelrequired | string | Display label for the item. |
onClick | () => void | Callback fired when the item is selected. |
isDisabled | boolean (default: false) | Whether the item is disabled; disabled items are skipped during keyboard navigation. |
icon | XDSIconType | Icon 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.| Prop | Type | Description |
|---|---|---|
typerequired | 'section' | Discriminant value that identifies this entry as a section. |
itemsrequired | XDSDropdownMenuItemData[] | The actionable items that belong to this section. |
title | string | Optional 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}><XDSDropdownMenubutton={{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}><XDSDropdownMenubutton={{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}><XDSDropdownMenubutton={{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}><XDSDropdownMenubutton={{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 (<XDSDropdownMenuisMenuOpen={isMenuOpen}onOpenChange={setIsMenuOpen}button={{label: 'Actions'}}items={[{label: 'Edit', onClick: () => {}},{label: 'Duplicate', onClick: () => {}},{label: 'Delete', onClick: () => {}},]}/>);}