Menu
Commands on a trigger — the ⋯ on a card, the account menu, a table row's actions — with the WAI-ARIA menu-button keyboard already wired.
Live
Code
<GdMenu :items="[
{ label: 'Open project', onSelect: open },
{ label: 'Duplicate', onSelect: duplicate },
{ label: 'Remove from project', danger: true, onSelect: remove }
]" label="Parcel actions">
<template #trigger="{ toggle, open }">
<GdButton icon label="Parcel actions" :aria-expanded="open" @click="toggle">
<GdIcon d="M5 12h.01M12 12h.01M19 12h.01" :size="16" :stroke="2.6" />
</GdButton>
</template>
</GdMenu>Keyboard
- Open — the first enabled item takes focus.
- ArrowDown / ArrowUp — rove between enabled items, wrapping at both ends. Disabled items are skipped.
- Home / End — jump to the first and last enabled item.
- Escape — closes and returns focus to whatever opened it. The keystroke is stopped, so a page-level handler does not also fire.
- Tab — closes the menu and moves on.
- Click outside — closes, and focus goes back to the trigger.
API
Props
itemsrequiredGdMenuItem[]—The commands, as DATA: `{ label, onSelect?, to?, badge?, separated?, danger?, disabled? }`. `separated` draws a rule above the item; `to` renders a link with menu semantics.
labelrequiredstring—The accessible name of the menu — "Parcel actions". Not optional: a `role="menu"` with no name is announced as "menu" and nothing else.
modelValuebooleanfalse`v-model` on the open state, for a caller that needs to open or close the menu itself. Left unbound the menu manages it.
placementGdPlacement"bottom"Which edge of the trigger the menu hangs from. A CONTEXT menu is this same contract on a different anchor — position a zero-size element at the pointer and pass `placement="bottom"` with `align="start"` to open down-and-right, the way every right-click menu does. Anchoring still flips near a viewport edge, so this is a preference rather than an instruction.
align"start" | "end" | "center""end"How the menu lines up along the trigger's other axis. `end` is right for a "⋯" at the end of a row, where a menu growing rightwards would leave the row.
Slots
triggerThe control that opens the menu. Scoped: receives `{ open, toggle }` — bind `:aria-expanded="open"` and `@click="toggle"`.
Usage Guidelines
- Use a menu when the item ACTS or NAVIGATES. Something that toggles state is a switch in a popover, not a menu item.
- Use
separatedon the one item that must not read as one more of the list above it — sign out, delete, leave. - Use
dangerfor a destructive item, and put it last. The action itself still confirms through a dialog. - Use
torather than anonSelectthat routes when the item is a destination. It renders a real link, so middle-click and open-in-new-tab work. - Use
disabled, not omission, when an item is unavailable for a reason the user could act on. A menu whose contents change shape between visits cannot be learned. - Reach for GdUserMenu instead for the account menu. It is this component with the identity chip, the destinations and sign-out already assembled.
- Bind
:aria-expanded="open"in the trigger slot. The menu supplies the value; the trigger is yours, so the attribute is yours to place.
Behavior & Anatomy
Why this is a component and not a popover with buttons in it
Arrow keys rove, Home and End jump, Escape closes and returns focus to the trigger, and the first item takes focus on open — the WAI-ARIA menu-button pattern. Hand-rolling that contract per call site is how a product ends up with six menus and four keyboards.
Items are data
Label, handler, tone — so the menu owns the keyboard and the caller owns the meaning. A menu item ACTS or NAVIGATES. Something that toggles state is a switch in a popover: a menu closes on selection, and a control the user wants to flip twice should not make them reopen the surface it lives on.
Destructive items sit last, under a rule
Destructive items take the danger ink and sit last — and the destructive action itself still confirms through a dialog. A slightly destructive action flush beneath a benign one is how people trigger it by accident, which is what separated exists to prevent.