Skip to content
View as Markdown

Disclosures

Menu

A list of actions opened from a trigger: the APG menu-button pattern on top of the browser's top layer and anchor positioning.

Import

import { Menu } from "@loamui/core";

Usage

Every example has a CSStab. That’s the real, complete stylesheet for the component: plain, static CSS, with nothing running in the browser.

Basic usage

Compose the menu from parts and wire each item's onClick to its action. The popup uses the same engine as Popover (native popover attribute and anchor positioning where supported, a wrapper-anchored fallback elsewhere) with the APG menu-button keyboard pattern on top: arrow keys rove focus, typing jumps to a matching item, and activating one closes the menu and returns focus to the trigger.

Groups and separators

Group related items under a label; Menu.Separator is a real <hr>. Contextual meaning is a custom property: declare --loam-context: danger on the group and the items inside adopt the danger accent, no props involved.

Disabled items

Disabled items use aria-disabled, so they stay visible to assistive technology but are skipped by roving focus and cannot be activated.

Guidance

When to use it

  • For a short list of actions on an object (rename, duplicate, export, delete) collapsed behind a single trigger.
  • When the actions are secondary enough that laying them all out as visible buttons would clutter the surface.

When not to

  • For choosing a value that persists, use Select, which has real selection semantics the menu role does not promise.
  • For navigation, prefer visible links; a menu hides destinations users need behind an extra interaction.
  • For one or two actions: plain Buttons are simpler and one click fewer.

Accessibility

  • Implements the APG menu-button pattern: ArrowDown/ArrowUp on the trigger open the menu and focus the first/last item; inside, arrow keys rove focus through the items (looping), Home/End jump to the ends, and typing jumps to the next item matching the query.
  • Escape closes and returns focus to the trigger, as does activating an item; Tab closes the menu and lets focus continue naturally: the menu moves focus, it never traps it.
  • The trigger is a real <button> with aria-haspopup="menu" and aria-expanded; the popup is role="menu" with role="menuitem" children, and Menu.Separator is a real <hr>: the platform's separator role, no ARIA needed.
  • Disabled items use aria-disabled rather than disabled, so they remain visible to assistive technology while roving focus skips them.
  • Where the popover attribute and anchor positioning are both supported, the browser provides top-layer rendering, light dismiss and Escape; other browsers get a wrapper-anchored fallback with the same behavior re-implemented, the deliberate no-polyfill trade-off (see the browser support policy in CONTRIBUTING).

How it works

Disabled items stay in the menu

A disabled item renders aria-disabled and is skipped by roving focus but stays visible and announced: the user learns the command exists and is currently unavailable. Removing it instead teaches them the feature is gone.

Destructive commands live in a labelled danger group

Put Delete and its kin in a Menu.Group with a GroupLabel, inside a --loam-context: danger wrapper. The separation and the colour both signal the stakes before the click, and the group label is announced with each item.

Icon-only triggers need a name

The ⋯ trigger reads as "menu" to a sighted user and as nothing to anyone else. Give it an aria-label naming the object it operates on ("Actions for INV-1024", not "menu"), because in a list of rows, ten triggers labelled "menu" are indistinguishable.

Parts