TypeScript · ESM · no runtime dependencies

A11y Menu Button

Disclosure-style menu button behavior for account menus, action panels, project switchers, settings, and other short collections of native controls.

At a glance

Quick facts

Package
a11y-menu-button
Runtime
Vanilla TypeScript / ESM
Dependencies
None
Semantics
Native interactive controls

Live demo

Try the interaction

Open any panel with pointer, touch, Enter, or Space. Arrow keys move through controls, Home and End jump, printable characters use typeahead, and Escape closes and restores focus.

Account links

Native links with bottom-start placement.

Edit actions

A classic three-dot button for common editing commands.

Destructive actions

Visual danger styling without changing button semantics.

Package setup

Installation

The repository is prepared for npm, but this page does not claim the package is published. After publication, install it with:

npm install a11y-menu-button

For local development, run npm install and npm run build:dist.

Native markup

Basic usage

HTML

<div class="a11y-menu-button" data-a11y-menu-button>
  <button
    class="a11y-menu-button__trigger"
    type="button"
    aria-expanded="false"
    aria-controls="account-actions">
    Account actions
  </button>
  <div class="a11y-menu-button__panel" id="account-actions" hidden>
    <a class="a11y-menu-button__item" href="#usage" data-menu-close>
      Profile
    </a>
  </div>
</div>

TypeScript

import { createMenuButton } from 'a11y-menu-button/core';
import 'a11y-menu-button/styles.css';

const root = document.querySelector(
  '[data-a11y-menu-button]'
);

if (root instanceof HTMLElement) {
  createMenuButton(root);
}

Configuration

Options and API

Explicit options override matching data-* values, which override defaults.

OptionTypeDefaultPurpose
closeOnEscapebooleantrueClose on Escape.
closeOnOutsidePointerbooleantrueClose after a pointer action outside.
closeOnFocusOutbooleantrueClose when focus leaves the component.
closeOnItemClickbooleanfalseClose for every item rather than only data-menu-close.
focusFirstOnOpenbooleanfalseMove focus to the first enabled control.
returnFocusOnEscapebooleantrueRestore trigger focus after Escape.
matchTriggerWidthbooleanfalseMatch the panel to the trigger width.
placementstringbottom-endChoose top/bottom and start/end alignment.
flipOnOverflowbooleantrueFlip vertically when space is limited.
maxPanelHeightbooleantrueConstrain the panel to available viewport space.
observeVisibilitybooleantrueClose or refresh when the root visibility changes.
typeaheadbooleantrueFind items by typing their label prefix.
typeaheadTimeoutnumber700Reset delay for the typeahead query.

Factories

createMenuButton() initializes one root. initMenuButtons() initializes marked descendants.

Instance methods

open(), close(), toggle(), isOpen(), refresh(), updatePlacement(), and destroy().

Lifecycle events

Typed init, before/open, before/close, item-click, refresh, addon-state, and destroy events bubble from the root. Only the two before-* hooks are cancelable; events do not cross Shadow DOM boundaries.

Filter announcements

announceResultCount is opt-in. Settled nonzero counts use one polite status after 250 ms; duplicate messages are skipped and the existing empty status owns zero results.

Verified behavior

Accessibility behavior

Keyboard interaction

KeyBehavior
Enter / SpaceUses native button activation to toggle the disclosure.
Space on a checkboxToggles the native checkbox and leaves the disclosure open. Enter keeps its native non-activation behavior.
ArrowDown / ArrowUpOpens from the trigger or moves through panel controls.
Home / EndMoves to the first or last enabled control.
EscapeCloses and returns focus to the trigger.
Printable characterMoves to the next control whose data, ARIA, native form, or text label starts with the query. Editable controls retain typing.
Tab / Shift + TabUses natural browser order and closes after focus leaves.

CSS API

Styling and themes

The default stylesheet provides layout, state, focus, and add-on styles. Public variables use the --a11y-menu-button-* prefix.

.product-menu {
  --a11y-menu-button-panel-min-width: 16rem;
  --a11y-menu-button-panel-radius: 0.5rem;
  --a11y-menu-button-panel-bg: #ffffff;
  --a11y-menu-button-panel-color: #172033;
}

Optional Soft, Compact, Elevated, and Command theme files are exported under a11y-menu-button/styles/themes/. Verify each product theme's foreground, background, interaction, danger, and focus colors.

The core stylesheet adds a CanvasText panel border in forced-colors mode because the default shadow may disappear. If a product overrides the panel border, enable a Windows Contrast theme and confirm that the open panel has one clear boundary at every supported placement and that focus indicators remain visible. Complete this manual check before mapping the result to WCAG 1.4.11.

Live pattern gallery

More patterns

These examples run on this page with the same compiled package as the primary demo. Try native buttons, links, checkboxes, filtering, asynchronous states, and full add-on teardown.

Document actions

Native action buttons; unavailable actions stay out of managed keyboard movement.

Checkbox preferences

Click a checkbox or its label, or focus it and press Space. Native checkboxes leave the disclosure open; Enter does not toggle them.

Filterable actions

A generated search field filters native buttons and announces settled result counts.

Asynchronous actions

The first local request fails on purpose; Retry then loads current actions.

Add-on lifecycle

Destroy generated add-on UI in reverse order, then safely initialize the same markup again.

Know the boundary

Limitations