Account links
Native links with bottom-start placement.
TypeScript · ESM · no runtime dependencies
Disclosure-style menu button behavior for account menus, action panels, project switchers, settings, and other short collections of native controls.
At a glance
a11y-menu-buttonLive demo
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.
Native links with bottom-start placement.
A classic three-dot button for common editing commands.
Visual danger styling without changing button semantics.
Package setup
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
<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>
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
Explicit options override matching data-* values, which override defaults.
| Option | Type | Default | Purpose |
|---|---|---|---|
closeOnEscape | boolean | true | Close on Escape. |
closeOnOutsidePointer | boolean | true | Close after a pointer action outside. |
closeOnFocusOut | boolean | true | Close when focus leaves the component. |
closeOnItemClick | boolean | false | Close for every item rather than only data-menu-close. |
focusFirstOnOpen | boolean | false | Move focus to the first enabled control. |
returnFocusOnEscape | boolean | true | Restore trigger focus after Escape. |
matchTriggerWidth | boolean | false | Match the panel to the trigger width. |
placement | string | bottom-end | Choose top/bottom and start/end alignment. |
flipOnOverflow | boolean | true | Flip vertically when space is limited. |
maxPanelHeight | boolean | true | Constrain the panel to available viewport space. |
observeVisibility | boolean | true | Close or refresh when the root visibility changes. |
typeahead | boolean | true | Find items by typing their label prefix. |
typeaheadTimeout | number | 700 | Reset delay for the typeahead query. |
createMenuButton() initializes one root. initMenuButtons() initializes marked descendants.
open(), close(), toggle(), isOpen(), refresh(), updatePlacement(), and destroy().
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.
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
aria-expanded, aria-controls, and the panel’s hidden state.role="menu", role="menuitem", or misleading popup semantics.destroy().| Key | Behavior |
|---|---|
| Enter / Space | Uses native button activation to toggle the disclosure. |
| Space on a checkbox | Toggles the native checkbox and leaves the disclosure open. Enter keeps its native non-activation behavior. |
| ArrowDown / ArrowUp | Opens from the trigger or moves through panel controls. |
| Home / End | Moves to the first or last enabled control. |
| Escape | Closes and returns focus to the trigger. |
| Printable character | Moves to the next control whose data, ARIA, native form, or text label starts with the query. Editable controls retain typing. |
| Tab / Shift + Tab | Uses natural browser order and closes after focus leaves. |
CSS API
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
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.
Native action buttons; unavailable actions stay out of managed keyboard movement.
Descriptive native links with secondary time information.
Click a checkbox or its label, or focus it and press Space. Native checkboxes leave the disclosure open; Enter does not toggle them.
A generated search field filters native buttons and announces settled result counts.
The first local request fails on purpose; Retry then loads current actions.
Destroy generated add-on UI in reverse order, then safely initialize the same markup again.
Know the boundary
aria-disabled communicates state but does not suppress link navigation or application handlers.