@gospore/dropdown-menu
v1.0.132
Published
A headless, fully keyboard accessible menu primitive used throughout the Design System. The package exports the following building blocks:
Readme
@gospore/dropdown-menu
A headless, fully keyboard accessible menu primitive used throughout the Design System. The package exports the following building blocks:
DropdownMenu– trigger + portal that positions content with Popper.js, teleports into the nearestThemeportal host (falling back to<body>), and traps focus when open.DropdownItem– basic option row that closes the menu when activated.DropdownSubmenu– nested menu row that opens a side-positioned submenu and coordinates close/focus behavior with its parent menu.DropdownItemCheckbox/DropdownItemRadio– stateful options that sync with checkbox or radio data structures.DropdownItemGroup– wraps related options, adds optional headings, and filters items when used insideSelect.
Provide your own trigger slot to replace the default button, or keep
text="…" for a ready-made trigger rendered with the Button component.
Set menuWidth when the popup should use a fixed width. Numbers are converted
to pixels; strings can use CSS units or design tokens. Use fitContainer when
the popup should instead match the trigger width.
Slots & events
Use listAriaLabel to give the popup surface its own accessible name. When omitted, the popup
continues to be labelled by the trigger. This is especially useful with listRole="dialog".
trigger(slotProps)– custom trigger; receivestoggleOpen,selected, and DOM ids to wire up ARIA attributes manually.- default slot – the menu content (usually a stack of item/group components).
- Emits
update:open(forv-model) andfocusoutwhen focus leaves both the trigger and the portal.
DropdownMenu exposes updatePosition() and closeDropdownMenu() via ref
for advanced integrations.
Large menus are positioned and height-constrained before they become visible, so
the panel does not flash at the viewport-sized fallback height on open. The
popup then uses a short 150ms fade and small surface translate on open, reverses
the same transition on close, and disables that motion under
prefers-reduced-motion.
Items use short, property-scoped transitions for hover and selection colors, focus-ring shadows, and the submenu's disabled opacity. Theme changes may briefly interpolate those properties; their settled light and dark states retain the documented contrast.
DropdownItem
A lightweight anchor that forwards attrs, closes its parent menu on click, and supports arrow-key activation inside Select.
Emits click and keydown so hosting components can react without reaching
into the DOM.
DropdownSubmenu
Use DropdownSubmenu inside a DropdownMenu or DropdownItemGroup when one
row should reveal another menu level. The default trigger uses role="menuitem",
aria-haspopup="menu", aria-expanded, and a right-chevron indicator.
<DropdownMenu text="Actions">
<DropdownItemGroup>
<DropdownItem @click="rename">Rename</DropdownItem>
<DropdownSubmenu label="Move to">
<DropdownItem @click="moveToInbox">Inbox</DropdownItem>
<DropdownItem @click="moveToArchive">Archive</DropdownItem>
</DropdownSubmenu>
</DropdownItemGroup>
</DropdownMenu>Nested menus can also be rendered recursively from data. Render items with
children as DropdownSubmenu and leaf items as DropdownItem; the parent
menu still coordinates focus, side placement, and close-on-select behavior for
the whole tree.
DropdownSubmenu supports v-model:open, id, label, disabled,
position (default right-start), flip, preventOverflow, openDelay,
closeDelay, and closeOnSelect. closeOnSelect defaults to root, so
activating a child item closes the whole dropdown tree. Set it to submenu to
close only the nested panel, or false to leave menus open after activation.
Provide a trigger slot for custom submenu rows. It receives triggerID,
dropdownMenuID, selected, disabled, open, close, toggleOpen, and
triggerProps so custom markup can preserve the built-in ARIA and event
behavior.
DropdownItemGroup
Wrap options in a titled section and optionally filter children. Select
leverages it to keep the menu height in sync.
Direct dropdown-item children may be rendered normally or through Vue
<template> fragments; filter, focus, and item-role handling is applied
consistently after fragments are flattened. Wrapper components are kept intact
and are responsible for forwarding any filtering state to the items they
render. Keep Select options as direct dropdown-item children so its keyboard
index matches the filtered rows; wrapper components are intended for regular
DropdownMenu content such as recursively rendered submenus.
The component exposes closeDropdownMenu() and updatePosition() helpers to
keep Popper aligned.
A titled group exposes the title as its accessible group name. The visible heading is hidden from the accessibility tree so menu and listbox ownership contains only valid item roles.
Checkbox & radio items
DropdownItemCheckbox accepts either a boolean or an array v-model, toggles the
entry, and automatically updates DropdownMenu’s Popper position when a wrapped
Select re-renders.
DropdownItemRadio mirrors radio-button behavior: v-model:checked receives
the currently selected value, and activating an option closes the menu (unless
used with puppetMode). Both variants accept disabled, filter,
filterValue, focused, and inputFocused props just like DropdownItem.
Accessibility
- The popup surface renders inside a portal with
role="menu"by default,role="listbox"whenlistRole="listbox", orrole="dialog"for composed popup controls. The default trigger wiresaria-haspopup,aria-controls, andaria-expanded. - A
listboxsurface makes nested plain, checkbox, and radio dropdown items exposerole="option"andaria-selected, including items rendered through wrapper components. - Focus is trapped inside the menu while open (unless
puppetModeis enabled) viafocus-trap, and ESC closes the menu + returns focus to the trigger. Open submenu panels are added to the root focus trap so teleported nested panels remain keyboard-contained. - Nested menus open with click,
Enter,Space, orArrowRight;ArrowLeftcloses the active submenu and restores focus to its trigger. - Scroll indicators fade in/out so users notice additional content while the panel keeps a constrained height.
Skeletons
This package also exports DropdownItemGroupSkeleton, DropdownItemSkeleton for async loading states. Import the owning package CSS plus @gospore/skeleton/skeleton.css so the shared skeleton primitive styles and tokens are available. Skeletons are decorative by default; put aria-busy, role="status", or loading copy on the surrounding region when assistive technology needs an explicit loading announcement.
