DropdownMenu
A component that displays a menu to the user with a list of actions or functions when the menu trigger is pressed.
Quick Start
- Installation
npm install @adaptavant/eds-core
- Import
import { DropdownMenu } from '@adaptavant/eds-core';
Default
The DropdownMenu and it's subcomponents can be composed to display a list of menu items that can trigger an action via the onClick
prop:
<DropdownMenu>
<DropdownMenuTrigger>Toggle dropdown menu</DropdownMenuTrigger>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Clicked profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked settings")}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</DropdownMenu>
Popover height & width control
You can set the maximum size of the DropdownMenu’s popovers using the popoverMaxHeight
and popoverMaxWidth
props:
<DropdownMenu popoverMaxHeight={100} popoverMaxWidth={150}>
<DropdownMenuTrigger>Toggle dropdown menu</DropdownMenuTrigger>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Clicked profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked settings")}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</DropdownMenu>
Popover placement
You can control the position of the popover relative to the trigger by using the popoverPlacement
prop. The default value is bottom-start
.
The distance between the trigger and the popover can be controlled using the popoverOffset
prop. The default value is 8.
<DropdownMenu
popoverOffset={16}
popoverPlacement="bottom" // bottom-start | bottom-end
>
<DropdownMenuTrigger>Toggle dropdown menu</DropdownMenuTrigger>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Clicked profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked settings")}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</DropdownMenu>
Custom trigger
You can provide the DropdownMenu with a custom trigger by passing a callback function to its children. This function provides access to both the button props required for the trigger to function correctly and be labeled accessibly, as well as the open state of the menu.
<DropdownMenu>
{({ triggerProps, isMenuOpen }) => (
<>
<IconButton
className="aria-expanded:bg-neutral-pressed"
icon={isMenuOpen ? LockOpenIcon : LockClosedIcon}
label="Toggle dropdown menu"
variant="neutralSecondary"
{...triggerProps}
/>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Account settings")}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</>
)}
</DropdownMenu>
Customizing a Custom Trigger:
Here's an example which uses an Avatar
and a wrapper Box
as custom trigger for the DropdownMenu
. Play around with it.
<DropdownMenu>
{({ triggerProps, isMenuOpen }) => (
<>
<Box
{...triggerProps}
className="p-1.5 rounded-full cursor-pointer leading-none hover:bg-neutral-secondary-hover focus-visible:focus-ring active:bg-neutral-secondary-pressed aria-expanded:bg-neutral-secondary-pressed"
tabIndex={0}
>
<Avatar name="Freddy" size="32">
<AvatarIcon icon={ProfileIcon} />
</Avatar>
</Box>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert('Profile')}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert('Messages')}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert('Account settings')}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</>
)}
</DropdownMenu>
You can see that although the custom trigger provided is a non-interactive element, it works as expected with all the accesibility features and UI states included. Here's a breakdown of the API and tokens involved in making a non-interactive trigger into an accessibie trigger.
triggerProps
- a prop that includes the neccessary a11y attributes and callbacks.isMenuOpen
- a prop that includes the open state of theDropdownMenu
.tabindex
- an HTML attribute that enables focus for an element. See MDN reference for further details.hover:*
- a CSS class utilty from Tailwindcss for styling the element on hover.active:*
- a CSS class utilty from Tailwindcss for styling the element when active.focus-visible:focus-ring
- a CSS class that adds a focus indicator to the element on keyboard focus.aria-expanded:*
- a CSS class utilty from Tailwindcss to style the element whenaria-expanded=true
Attention 🚧 : Though we are able to customize the custom trigger for any element. It is recommended to use only focusable element as trigger for the DropdownMenu
Strategy
Use the strategy
prop to change the way how popover element will be positioned. By default, the strategy is set to absolute
, changes it to fixed
when the DropdownMenuTrigger is inside a sticky or fixed element.
This option leverages the floating-ui library, which powers the DropdownMenuPopover functionality.
const [showFixedElement, setShowFixedElement] = React.useState(false);
const onButtonClick = () => {
setShowFixedElement((prevState) => !prevState)
}
return (
<Stack className="w-full gap-4">
{showFixedElement ? (
<div
className="
animate-[snackbar-transition_0.3s_cubic-bezier(0.16,_1,_0.3,_1)]
bg-neutral-secondary
fixed
flex
items-center
justify-between
mx-2
p-4
right-0
rounded-8px
shadow-40
sm:right-8
sm:w-[360px]
top-8
w-[calc(100%-16px)]
z-10
"
>
<DropdownMenu strategy="fixed">
{({ triggerProps, isMenuOpen }) => (
<>
<DropdownMenuTrigger>Toggle dropdown</DropdownMenuTrigger>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Account settings")}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</>
)}
</DropdownMenu>
<button
className="
focus-visible:focus-ring
font-stronger
px-1
py-0.5
rounded-4px
text-body-12
text-primary
underline
underline-offset-2
"
onClick={onButtonClick}
>
Close
</button>
</div>
) : null}
<Button onClick={onButtonClick}>
Show fixed element
</Button>
</Stack>
);
Disabled
Utilize the isDisabled
prop in the <Field/>
to show that a entire DropdownMenu isn't usable.
<DropdownMenu isDisabled>
<DropdownMenuTrigger>Toggle dropdown menu</DropdownMenuTrigger>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Clicked profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked settings")}>
Account settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</DropdownMenu>
Disabled MenuItem
Utilize the isDisabled
prop in the <DropdownMenuItem />
component to indicate that a specific item is not selectable.
This enhances user experience by clearly displaying the disabled state. Additionally, for improved accessibility, keyboard navigation will bypass disabled MenuItems in the Listbox.
<DropdownMenu>
<DropdownMenuTrigger>Toggle dropdown menu</DropdownMenuTrigger>
<DropdownMenuPopover>
<DropdownMenuList>
<DropdownMenuItem onClick={() => alert("Clicked profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem isDisabled onClick={() => alert("Clicked messages")}>
Messages
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked settings")}>
Account settings
</DropdownMenuItem>
<DropdownMenuItem onClick={() => alert("Clicked personal settings")}>
Personal settings
</DropdownMenuItem>
</DropdownMenuList>
</DropdownMenuPopover>
</DropdownMenu>
Note: Disabled item can already be selected option but having any interactions on it won't be possible.
Style API
Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.
Please refer to the Style API documentation for more insights.