Agriculture Design System
Beta
Design System for the Export Service

Dropdown menu

A dropdown menu displays a list of actions when a trigger is pressed.

View in StorybookView in Github
import { ... } from '@ag.ds-next/react/dropdown-menu';
Open in Playroom, opens in a new tab
<DropdownMenu>
	<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuItem onClick={() => console.log('Profile')}>
			Profile
		</DropdownMenuItem>
		<DropdownMenuItem onClick={() => console.log('Messages')}>
			Messages
		</DropdownMenuItem>
		<DropdownMenuItem onClick={() => console.log('Account settings')}>
			Account settings
		</DropdownMenuItem>
	</DropdownMenuPanel>
</DropdownMenu>

A dropdown menu displays a list of actions when triggered by a DropdownMenuButton. Dropdown menus float on top of the content they sit on. Consider using them when there isn’t sufficient space to display actions.

Dropdown menus pose multiple usability problems which you should consider before using them:

  • since the actions in a dropdown menu are hidden by default, they could be missed.
  • dropdown menus can increase cognitive load, as users need to recall what the actions were. This can be especially problematic for users with cognitive impairments.
  • due to their fiddly nature, some users can struggle to use dropdown menus. Elderly people and people with motor impairments, in particular, often have difficulty using dropdown menus.

Do

  • ensure that actions in the dropdown can be accessed elsewhere on the same page, as some users won’t be able to access them via the dropdown menu
  • only use a dropdown menu if there’s insufficient space to display actions
  • trigger a dropdown menu on click of an element
  • keep the dropdown menu open until a user has made a selection or clicked outside the menu
  • organise actions in a logical way
  • consider listing actions in alphabetical order by default
  • group related actions
  • consider setting a maximum height when there are 10 or more actions.

Don’t

  • use a dropdown menu if there’s space to display actions
  • trigger a dropdown menu on hover of an element, ensure it’s triggered on click
  • use in the Main nav or other nav components
  • use a dropdown menu in a form where users need to select a single option - use a Select component instead.

Triggers

Dropdown menus can be triggered by a DropdownMenuButton, which can accept all of the same props as Button. This gives you flexibility in what your trigger looks like.

<Stack gap={1}>
	{['md', 'sm'].map((size) => (
		<ButtonGroup key={size}>
			{['primary', 'secondary', 'tertiary', 'text'].map((variant) => (
				<DropdownMenu key={variant}>
					<DropdownMenuButton variant={variant} size={size}>
						Actions
					</DropdownMenuButton>
					<DropdownMenuPanel>
						<DropdownMenuItem>Item A</DropdownMenuItem>
						<DropdownMenuItem>Item B</DropdownMenuItem>
						<DropdownMenuItem>Item C</DropdownMenuItem>
					</DropdownMenuPanel>
				</DropdownMenu>
			))}
		</ButtonGroup>
	))}
</Stack>

Placement

By default the dropdown menu is aligned to the bottom left (bottom-start) of the DropdownMenuButton when opened. It can alternatively be aligned to the bottom right (bottom-end) or the bottom (bottom). If there’s not enough space in the browser window to align the menu on the left, the dropdown menu will automatically be aligned to the right to help ensure it fits on the screen.

<Stack gap={2} alignItems="center">
	{['bottom-start', 'bottom-end', 'bottom'].map((size) => (
		<DropdownMenu key={size} popoverPlacement={size}>
			<DropdownMenuButton>{size}</DropdownMenuButton>
			<DropdownMenuPanel>
				<DropdownMenuItem>Profile</DropdownMenuItem>
				<DropdownMenuItem>Messages</DropdownMenuItem>
				<DropdownMenuItem>Account settings</DropdownMenuItem>
			</DropdownMenuPanel>
		</DropdownMenu>
	))}
</Stack>

Text only

The simplest dropdown menu contains text only list items. Use DropdownMenuItem to perform on-page actions and DropdownMenuItemLink to link to separate pages.

<DropdownMenu>
	<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuItem onClick={() => console.log('Profile')}>
			Profile
		</DropdownMenuItem>
		<DropdownMenuItem onClick={() => console.log('Messages')}>
			Messages
		</DropdownMenuItem>
		<DropdownMenuItemLink href="#">
			Account settings
		</DropdownMenuItem>
	</DropdownMenuPanel>
</DropdownMenu>

If you’re opening links in a new tab, we recommend you use the ExternalLinkIcon.

<DropdownMenu>
	<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuItemLink href="#link-1">Link 1</DropdownMenuItemLink>
		<DropdownMenuItemLink href="#link-2">Link 2</DropdownMenuItemLink>
		<DropdownMenuItemLink
			href="https://www.agriculture.gov.au"
			target="_blank"
			icon={ExternalLinkIcon}
		>
			External link
		</DropdownMenuItemLink>
	</DropdownMenuPanel>
</DropdownMenu>

Icons and badges

Icons can be added to list items to help users recognise actions more quickly. Use the icon prop to add an Icon to the beginning of list items.

Use the endElement prop to add elements such as an Indicator dot or Notification badge to the end of list items.

<DropdownMenu>
	<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuItem icon={AvatarIcon}>Profile</DropdownMenuItem>
		<DropdownMenuItem
			icon={EmailIcon}
			endElement={
				<React.Fragment>
					<NotificationBadge value={100} max={99} tone="action" aria-hidden />
					<VisuallyHidden>, 100 unread</VisuallyHidden>
				</React.Fragment>
			}
		>
			Messages
		</DropdownMenuItem>
		<DropdownMenuItem icon={SettingsIcon}>Account settings</DropdownMenuItem>
	</DropdownMenuPanel>
</DropdownMenu>

Divider

Use a DropdownMenuDivider to separate destructive actions at the bottom of the list. This helps ensure they’re less likely to be actioned by mistake.

<DropdownMenu>
	<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuItem icon={AvatarIcon}>Profile</DropdownMenuItem>
		<DropdownMenuItem
			icon={EmailIcon}
			endElement={
				<React.Fragment>
					<NotificationBadge value={100} max={99} tone="action" aria-hidden />
					<VisuallyHidden>, 100 unread</VisuallyHidden>
				</React.Fragment>
			}
		>
			Messages
		</DropdownMenuItem>
		<DropdownMenuItem icon={SettingsIcon}>Account settings</DropdownMenuItem>
		<DropdownMenuDivider />
		<DropdownMenuItem icon={ExitIcon}>Sign out</DropdownMenuItem>
	</DropdownMenuPanel>
</DropdownMenu>

Grouping

Consider grouping long lists of items under headings to help decrease cognitive load. Each group should be separated by a DropdownMenuDivider.

Use the DropdownMenuGroup component to group related items. Use the label prop to describe the group.

<DropdownMenu>
	<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuGroup label="Services">
			<DropdownMenuItem icon={PrintIcon}>Print online</DropdownMenuItem>
			<DropdownMenuItem icon={CalendarIcon}>Scheduler</DropdownMenuItem>
		</DropdownMenuGroup>
		<DropdownMenuDivider />
		<DropdownMenuGroup label="Account">
			<DropdownMenuItem icon={AvatarIcon}>Profile</DropdownMenuItem>
			<DropdownMenuItem
				icon={EmailIcon}
				endElement={
					<React.Fragment>
						<NotificationBadge value={100} max={99} tone="action" aria-hidden />
						<VisuallyHidden>, 100 unread</VisuallyHidden>
					</React.Fragment>
				}
			>
				Messages
			</DropdownMenuItem>
			<DropdownMenuItem icon={SettingsIcon}>Account settings</DropdownMenuItem>
		</DropdownMenuGroup>
		<DropdownMenuDivider />
		<DropdownMenuItem icon={ExitIcon}>Sign out</DropdownMenuItem>
	</DropdownMenuPanel>
</DropdownMenu>

Radio groups

Use the DropdownMenuGroup and DropdownMenuRadioItem components to create a list of options that users can switch between. Only 1 item can be selected at a time, similar to a list of Radios.

Use the DropdownMenuGroupLink component to add a link to the bottom of the Radio group.

() => {
	const [selectedItem, setSelectedItem] = React.useState('');
	return (
		<Stack gap={2} alignItems="flex-start">
			<DropdownMenu>
				<DropdownMenuButton>Toggle dropdown menu</DropdownMenuButton>
				<DropdownMenuPanel>
					<DropdownMenuGroup label="Businesses">
						<DropdownMenuItemRadio
							checked={selectedItem === 'Antfix'}
							onClick={() => setSelectedItem('Antfix')}
							secondaryText="Sydney"
						>
							Antfix
						</DropdownMenuItemRadio>
						<DropdownMenuItemRadio
							checked={selectedItem === 'Produce Fresh'}
							onClick={() => setSelectedItem('Produce Fresh')}
							secondaryText="Brisbane"
						>
							Produce Fresh
						</DropdownMenuItemRadio>
						<DropdownMenuItemRadio
							checked={selectedItem === 'Organic Co'}
							onClick={() => setSelectedItem('Organic Co')}
							secondaryText="Canberra"
						>
							Organic Co
						</DropdownMenuItemRadio>
						<DropdownMenuGroupLink href="#">View all</DropdownMenuGroupLink>
					</DropdownMenuGroup>
				</DropdownMenuPanel>
			</DropdownMenu>
			<Text>
				Currently selected: {selectedItem ? selectedItem : 'Unselected'}
			</Text>
		</Stack>
	);
};

Overflow

For dropdown menus with many items, consider setting a maximum height using the maxHeight prop. This will make the actions in the dropdown menu scrollable. If the height of the actions list is taller than the screen height, the dropdown menu will automatically resize to ensure that it fits on the screen.

<Stack gap={2} alignItems="flex-start">
	<DropdownMenu>
		<DropdownMenuButton>
			Toggle dropdown menu (without max height)
		</DropdownMenuButton>
		<DropdownMenuPanel>
			{Array.from(Array(20).keys()).map((i) => (
				<DropdownMenuItem key={i}>Item {i + 1}</DropdownMenuItem>
			))}
		</DropdownMenuPanel>
	</DropdownMenu>
	<DropdownMenu popoverMaxHeight={288}>
		<DropdownMenuButton>
			Toggle dropdown menu (with max height)
		</DropdownMenuButton>
		<DropdownMenuPanel>
			{Array.from(Array(20).keys()).map((i) => (
				<DropdownMenuItem key={i}>Item {i + 1}</DropdownMenuItem>
			))}
		</DropdownMenuPanel>
	</DropdownMenu>
</Stack>

Offset

Use the popoverOffset prop to control the vertical distance between the button and panel. This value needs to be specified in pixels

<DropdownMenu popoverOffset={-8}>
	<DropdownMenuButton variant="primary">
		Toggle dropdown menu
	</DropdownMenuButton>
	<DropdownMenuPanel>
		<DropdownMenuItem onClick={() => console.log('Profile')}>
			Profile
		</DropdownMenuItem>
		<DropdownMenuItem onClick={() => console.log('Messages')}>
			Messages
		</DropdownMenuItem>
		<DropdownMenuItem onClick={() => console.log('Account settings')}>
			Account settings
		</DropdownMenuItem>
	</DropdownMenuPanel>
</DropdownMenu>

Accessing state

If you need to access the internal state of the menu, you can provide a render callback to DropdownMenu. This is useful if you want to change the appearance of the dropdown menu button based on whether the dropdown menu is opened or closed.

<DropdownMenu>
	{({ isMenuOpen }) => (
		<React.Fragment>
			<DropdownMenuButton
				iconAfter={isMenuOpen ? ChevronUpIcon : ChevronDownIcon}
			>
				{isMenuOpen ? 'Close dropdown menu' : 'Open dropdown menu'}
			</DropdownMenuButton>
			<DropdownMenuPanel>
				<DropdownMenuItem>Profile</DropdownMenuItem>
				<DropdownMenuItem>Messages</DropdownMenuItem>
				<DropdownMenuItem>Account settings</DropdownMenuItem>
			</DropdownMenuPanel>
		</React.Fragment>
	)}
</DropdownMenu>
  • Combobox This component allows users to select from a list of options. It’s especially useful when there are many options to choose from.
  • Select Select provides a way for users to choose one item from a collapsible list. It helps reduce input errors and screen space.