Alternatives
- SelectMenu — For presenting a list of selectable options to a user
Primitives
This package exposes a number of primitives that can be used to compose different parts of a dropdown menu. Below are the things that can be composed with these primitives.
Dropdown
Renders the root of a dropdown menu.
Basic Usage
All dropdown primitives must be used as children of this component.
<Dropdown>{/* dropdown primitives */}</Dropdown>
defaultOpen
Controls the default open/closed state of a dropdown menu. This prop only controls the initial state of a dropdown, see the following section in order to full control the open/closed state of a dropdown.
<Dropdown defaultOpen={true}></Dropdown>
programmatic control of open/closed state
The open
and onOpenChange
props can be used to manually control the open/closed state of a dropdown menu, making it behave like a controlled input.
const [open, setIsOpen] = useState(open);return <Dropdown open={open} onOpenChange={setIsOpen}></Dropdown>;
align
Sets the horizontal alignment of the dropdown relative to the trigger.
Accepts 3 values: start
, center
, end
. Default value is center
.
<Dropdown align="start"></Dropdown>
verticalOffset
Sets the vertical offset of the dropdown relative to the trigger.
A positive value moves the dropdown lower, a negative value moves the dropdown higher.
<Dropdown verticalOffset={-5}></Dropdown>
itemHeight
Optionally set the height of each item renderred by Dropdown.Item
. This value is applied to all items, it's not possible to have items of different height.
Default value 40
.
Used to make the items taller in order to give the illusion of more space between items.
<Dropdown itemHeight={56}></Dropdown>
viewportItemCount
Number of items to render in the menu before a scroll bar appears.
Default value 8
.
<Dropdown viewportItemCount={5}></Dropdown>
Dropdown.Trigger
Renders an interactive element, taken as a child, that controls the open/closed state of a dropdown menu.
Basic Usage
The contents of a trigger can be passed as children which should be a string in most cases.
This component can be used in 2 flavors:
1 - via Attributes: By default, this component will write the following attributes to the child component provided: aria-haspopup
and data-closed
. Consumers can use the data-closed
attribute via css to style the open/closed state of their triggers. The css would look something like:
{ background: 'grey'; `&[data-closed=true]`: { background: 'blue'; }}
<Dropdown.Trigger> <Dropdown.TriggerButton label="Items" /></Dropdown.Trigger>
2 - via Function: If the child provided to this component is a function, it will be executed with the following parameters.
<Dropdown.Trigger> {({ isOpen, triggerProps }: CustomTriggerProps) => { return ( <IconButton aria-label="Customise options" icon={MenuIcon} label="Customise options" {/* Use the isOpen prop to style trigger correctly. */} colorScheme={isOpen ? 'primary' : 'tertiary'} {/* Make sure to pass the trigger props. */} {...triggerProps} /> ); }}</Dropdown.Trigger>
The first parameter represents the open/closed state of the menu which can be used to style the trigger.
The second parameter is props that need to be passed to your trigger in order for the component to determine the open/closed state correctly. This parameter contains a ref that must be passed correctly to the interactive DOM element, which is a button in the above example. The open/closed state will not correctly if the ref is not applied properly to the trigger.
Dropdown.TriggerButton
Renders a trigger button that seamlessly works with the Dropdown.Trigger
primitive.
<Dropdown.Trigger> <Dropdown.TriggerButton label="Dropdown" /></Dropdown.Trigger>
Dropdown.Item
This primitive represents a single clickable item in a dropdown menu.
Basic Usage
The contents of a menu item can be passed as children which should be a string in most cases.
<Dropdown.Item onSelect={/* do stuff */}>Will Smith</Dropdown.Item>
startElement
Renders a ReactNode
to the left of the children.
<Dropdown.Item startElement={<UserAvatar size="xsmall" name="Will Smith" />}> Will Smith</Dropdown.Item>
endElement
Renders a ReactNode
to the right of children.
<Dropdown.Item endElement={<Badge tone="positive" label="Active" />}> Will Smith</Dropdown.Item>
tone
Sets the tone of the menu item to one of: passive
, critical
.
The default tone is passive
.
<Dropdown.Item tone="critical">Delete user</Dropdown.Item>
disabled
Disables the menu item.
<Dropdown.Item disabled="critical">Will Smith</Dropdown.Item>
onSelect
Emits an event when user clicks on a menu item. Use this to perform actions.
<Dropdown.Item onSelect={() => { /* do stuff */ }}> Will Smith</Dropdown.Item>
href
Converts the menu item to a link.
<Dropdown.Item href="#">Will Smith</Dropdown.Item>
target
A supporting prop for when using the menu item as a link.
Setting target=_blank
will render the link as an external link, it will have pop out icon as an endElement
.
<Dropdown.Item href="#" target="_blank"> Will Smith</Dropdown.Item>
Note: A menu item with
href
andtarget=blank
will not render a customendElement
if provided. It renders an external link icon instead.
Dropdown.SubMenu
Renders a nested submenu in a dropdown.
Basic Usage
The contents of a submenu item can be passed as children which should be a string in most cases.
<Dropdown.SubMenu>{/* submenu primitives */}</Dropdown.SubMenu>
defaultOpen
Controls the default open/closed state of a dropdown menu. This prop only controls the initial state of a dropdown, see the following section in order to full control the open/closed state of a dropdown.
<Dropdown.SubMenu defaultOpen={true}></Dropdown.SubMenu>
programmatic control of open/closed state
The open
and onOpenChange
props can be used to manually control the open/closed state of a dropdown menu, making it behave like a controlled input.
const [open, setIsOpen] = useState(open);return ( <Dropdown.SubMenu open={open} onOpenChange={setIsOpen}></Dropdown.SubMenu>);
Dropdown.SubMenuTrigger
Renders an interactive element that provides access to a submenu. This component mostly behaves like a Dropdown.Item
with minor differences.
Basic Usage
The contents of a submenu trigger can be passed as children which should be a string in most cases.
<Dropdown.SubMenuTrigger>submenu</Dropdown.SubMenuTrigger>
startElement
Renders a ReactNode
to the left of children.
<Dropdown.SubMenuTrigger startElement={<UserAvatar size="small" name="submenu" />}> >submenu</Dropdown.SubMenuTrigger>
tone
Sets the tone of the menu item to one of: passive
, critical
.
The default tone is passive
.
<Dropdown.SubMenuTrigger tone="critical">submenu</Dropdown.SubMenuTrigger>
disabled
Disables the menu item, preventing the user from interacting with the submenu.
<Dropdown.SubMenuTrigger disabled>submenu</Dropdown.SubMenuTrigger>
selected & selectedLabel
For cases when a submenu trigger wants to show that an item from its submenu has been selected. We can use the selected
and selectedLabel
props to achieve make the submenu trigger behave like a radio item.
Use the createTriggerSelectedLabel
to generate the selectedLabel
value.
<Dropdown.SubMenuTrigger selected={Number(selected) > 2} selectedLabel={createTriggerSelectedLabel('Group')} startElement={<UserAvatar size="xsmall" name="2" />}> Group</Dropdown.SubMenuTrigger>
Check out this section for a detailed example.
Dropdown.RadioGroup
Renders a list of menu items as radios for the user to make a selection.
Basic Usage
Menu radio items must be used in side Dropdown.RadioGroup
.
const [value, setValue] = useState('0');return ( <Dropdown.RadioGroup title="Radio Group" value={value} onValueChange={setValue} > {/* radio group primitives */} </Dropdown.RadioGroup>);
title
Title of the radio group.
value
The currently selected value of the radio group. This value must match one of the child radio items.
onValueChange
Emits the value of the item selected by the user. Use this to update state.
Dropdown.RadioItem
A menu item that behaves like a radio. Can only be used inside Dropdown.RadioGroup
.
Basic Usage
The value that's emitted when user selects the radio menu item.
The contents of a radio menu item can be passed as children which should be a string in most cases.
Dropdown.RadioItem
can only be used inside Dropdown.RadioGroup
.
<Dropdown.RadioItem value="will_smith">Will Smith</Dropdown.RadioItem>
Dropdown.Divider
Use to draw a horizontal divider between items to create visual grouping.
<Dropdown.Item>Item 1<Dropdown.Item><Dropdown.Item>Item 2<Dropdown.Item><Dropdown.Divider /><Dropdown.Item>Item 3<Dropdown.Item><Dropdown.Item>Item 4<Dropdown.Item>
Dropdown.MenuTitle
Use to render label for the menu.
<Dropdown.MenuTitle>Group Title</Dropdown.MenuTitle><Dropdown.Item>Item 1<Dropdown.Item><Dropdown.Item>Item 2<Dropdown.Item><Dropdown.Item>Item 3<Dropdown.Item>
Note: A dropdown menu can only have 1 menu title.
Dropdown.GroupTitle
Use to render label for a group of items.
<Dropdown.Item>Item 1<Dropdown.Item><Dropdown.Item>Item 2<Dropdown.Item><Dropdown.GroupTitle>Group Title</Dropdown.GroupTitle><Dropdown.Item>Item 3<Dropdown.Item><Dropdown.Item>Item 4<Dropdown.Item>
It can be used in combination with Dropdown.Divider
to create a visually distinct group.
<Dropdown.Item>Item 1<Dropdown.Item><Dropdown.Item>Item 2<Dropdown.Item><Dropdown.Divider /><Dropdown.GroupTitle>Group Title</Dropdown.GroupTitle><Dropdown.Item>Item 3<Dropdown.Item><Dropdown.Item>Item 4<Dropdown.Item>
Testing
The dropdown component and its primitives support the data
attribute to facilitate testing with some extras. It will prefix testids with the parent testids (if provided), recursively for a better developer experience.
The prefix behaviour is only applied to the data attribute testid
, all other data attributes are passed as is.
Below are annotated samples of all the primitives that support the data
attribute and nesting testid.
/** Supports testid. 'testable-dropdown' will the the testid of the root dropdown element, all accessible children will be prefixed with it */<Dropdown data={{ testid: 'testable-dropdown' }}> {/** No need to apply testid to triggers, it will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-trigger' */} <Dropdown.Trigger>...</Dropdown.Trigger> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-item-1' */} <Dropdown.Item data={{ testid: 'item-1' }}>...</Dropdown.Item> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-item-2' */} <Dropdown.Item data={{ testid: 'item-2' }}>...</Dropdown.Item> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-radio-group-1'. All accessible children of this element will be prefixed with 'testable-dropdown-radio-group-1' */} <Dropdown.RadioGroup data={{ testid: 'radiogroup-1' }}> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-radio-group-1-item-1' */} <Dropdown.RadioItem data={{ testid: 'item-1' }}>...</Dropdown.RadioItem> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-radio-group-1-item-2' */} <Dropdown.RadioItem data={{ testid: 'item-2' }}>...</Dropdown.RadioItem> </Dropdown.RadioGroup> {/** SubMenu behaves similar to root Dropdown in terms of testid except that it is prefixed with parent testid based on context. The testid for this item will be 'testable-dropdown-submenu-1'. All accessible children of this element will be prefixed with 'testable-dropdown-submenu-1'. */} <Dropdown.SubMenu data={{ testid: 'submenu-1' }}> {/** No need to apply testid to triggers, it will be prefixed with the parent testid. The testid for this item will be 'testable-dropdown-submenu-1-trigger' */} <Dropdown.SubMenuTrigger>Submenu</Dropdown.SubMenuTrigger> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-submenu-1-item-1' */} <Dropdown.Item data={{ testid: 'item-1' }} onSelect={() => alert('Selected submenu 1 item 1')} > Submenu 1 Item 1 </Dropdown.Item> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-submenu-1-item-2' */} <Dropdown.Item data={{ testid: 'item-2' }} onSelect={() => alert('Selected submenu 1 item 2')} > Submenu 1 Item 2 </Dropdown.Item> {/** Nested SubMenu behaves similar to root Dropdown in terms of testid except that it is prefixed with parent testid based on context. The testid for this item will be 'testable-dropdown-submenu-1-nestedsubmenu-1'. All accessible children of this element will be prefixed with 'testable-dropdown-submenu-1-nestedsubmenu-1'.*/} <Dropdown.SubMenu data={{ testid: 'nestedsubmenu-1' }}> {/** No need to apply testid to triggers, it will be prefixed with the parent testid. The testid for this item will be 'testable-dropdown-submenu-1-nestedsubmenu-1-trigger' */} <Dropdown.SubMenuTrigger>Nested Submenu</Dropdown.SubMenuTrigger> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-submenu-1-nestedsubmenu-1-item-1' */} <Dropdown.Item data={{ testid: 'item-1' }} onSelect={() => alert('Selected nested submenu 1 item 1')} > Nested Submenu 1 Item 1 </Dropdown.Item> {/** Supports testid. The will be prefixed with the parent testid based on context. The testid for this item will be 'testable-dropdown-submenu-1-nestedsubmenu-1-item-2' */} <Dropdown.Item data={{ testid: 'item-2' }} onSelect={() => alert('Selected nested submenu 1 item 2')} > Nested Submenu 1 Item 2 </Dropdown.Item> </Dropdown.SubMenu> </Dropdown.SubMenu></Dropdown>
Inspect the recipes below to see the testids in action.
Receipes
Full examples of how to compose dropdown menus of varying complexity.
Simple items
Custom trigger
Start element items
Radio items
Nested dropdown menus
Custom grouping
App switcher
A dropdown menu that's effectively a single radio group, but nested.