Dropdown menu
Playground
TSX
'use client';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuItemIndicator,
Icon,
Avatar,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
Button,
type DropdownMenuItemSize,
type DropdownMenuItemVariant,
} from '@mbsw/ui';
import { Check, Ellipsis } from 'lucide-react';
export default function Demo() {
const settings = {
"leadingIcon": false,
"mode": "items",
"variant": "default",
"size": "md",
"disabled": false,
"showValue": true
};
const [open, setOpen] = useState(true);
const [subOpen, setSubOpen] = useState(false);
const windowBlur = useRef(false);
useEffect(() => {
const onBlur = () => {
windowBlur.current = true;
queueMicrotask(() => {
windowBlur.current = false;
});
};
// Only suppress dismissal during the iframe's blur event, not the next
// pointer interaction that focuses the preview again.
window.addEventListener('blur', onBlur, true);
return () => window.removeEventListener('blur', onBlur, true);
}, []);
const [checked, setChecked] = useState<number[]>([0]);
const [menu, setMenu] = useState<HTMLDivElement | null>(null);
const [menuHeight, setMenuHeight] = useState(120);
useLayoutEffect(() => {
if (!menu) return;
const measure = () => setMenuHeight(menu.offsetHeight);
measure();
const observer = new ResizeObserver(measure);
observer.observe(menu);
return () => observer.disconnect();
}, [menu]);
const menuWidth =
settings.mode === 'submenu' ? 'min(180px, calc((100vw - 64px) / 2))' : 180;
const items = ['Option A', 'Option B', 'Option C'];
const leadingAvatar = (label: string) =>
settings.leadingIcon ? (
<span data-slot="leading" aria-hidden="true" style={{ display: 'flex' }}>
<Avatar size="sm" label={label} initials={label.slice(-1)} />
</span>
) : null;
const common = {
size: settings.size as DropdownMenuItemSize,
variant: settings.variant as DropdownMenuItemVariant,
disabled: settings.disabled,
};
return (
<div style={{ position: 'relative', width: menuWidth, height: menuHeight }}>
<DropdownMenu
open={open}
modal={false}
onOpenChange={(next) => {
// Keep the preview open when focus moves to the surrounding controls.
// Selection, Escape and outside clicks inside this document still close it.
if (next || !windowBlur.current) setOpen(next);
}}
>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
tone="secondary"
iconOnly
icon={Ellipsis}
aria-label={"Open menu"}
style={{
position: 'absolute',
bottom: 'calc(100% + 8px)',
left: '50%',
transform: 'translateX(-50%)',
}}
/>
</DropdownMenuTrigger>
<DropdownMenuContent
ref={setMenu}
side="bottom"
align="center"
sideOffset={8}
style={{ width: menuWidth }}
>
{settings.mode === 'submenu' ? (
<DropdownMenuSub
open={subOpen}
onOpenChange={(next) => {
if (next || !windowBlur.current) setSubOpen(next);
}}
>
<DropdownMenuSubTrigger
size={settings.size as DropdownMenuItemSize}
disabled={settings.disabled}
textValue="Options"
>
{leadingAvatar('Options')}
Options
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
sideOffset={13}
alignOffset={-5}
collisionPadding={8}
>
{items.map((label, index) => (
<DropdownMenuItem {...common} key={index}>
{leadingAvatar(label)}
{label}
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
) : (
items.map((label, index) =>
settings.mode === 'checkbox' ? (
<DropdownMenuCheckboxItem
{...common}
key={index}
textValue={label}
checked={checked.includes(index)}
onSelect={(event) => event.preventDefault()}
onCheckedChange={(value) =>
setChecked((current) =>
value
? [...current, index]
: current.filter((i) => i !== index),
)
}
>
{leadingAvatar(label)}
{label}
<DropdownMenuItemIndicator>
<Icon icon={Check} size="sm" decorative />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
) : (
<DropdownMenuItem
{...common}
key={index}
description={settings.showValue ? 'Description' : undefined}
>
{leadingAvatar(label)}
{label}
</DropdownMenuItem>
),
)
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
Properties
Size
Leading icon
Items
Item variant
Description
Disabled
Usage
Use DropdownMenu for actions and Select for form values. In this example, set Items to checkbox to let users check multiple items. The check appears on the right. Option A is checked by default, and selecting an item keeps the menu open. Add selection indicators with DropdownMenuItemIndicator. DropdownMenuContent renders in a portal.
TSX
'use client';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
Button,
} from '@mbsw/ui';
export default function Example() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>{"Open menu"}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem description="Description">Option A</DropdownMenuItem>
<DropdownMenuItem description="Description">Option B</DropdownMenuItem>
<DropdownMenuItem description="Description">Option C</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
The Playground Code tab includes the full example with your current settings. See Installation for the CSS imports.