Skip to content
Components

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

PropTypeDefault
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

trigger

The 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 separated on the one item that must not read as one more of the list above it — sign out, delete, leave.
  • Use danger for a destructive item, and put it last. The action itself still confirms through a dialog.
  • Use to rather than an onSelect that 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.

Navigate

Esc