Overlay · astro-ui · MenuNew
MenuJS
Floating action menu with the WAI-ARIA menu-button keyboard model.
Menu is the “⋯” action list: a trigger button that opens a role="menu" panel of actions. MenuItem renders a real <button> or <a>, so activation, middle-click, and copy-link all behave like normal HTML. The scoped script adds the parts native HTML cannot express — roving focus, arrow/Home/End navigation, typeahead, Escape-to-close with focus return, and outside-click dismissal.
Features
- WAI-ARIA menu-button pattern: aria-haspopup, aria-expanded, role=menu
- Arrow keys with wraparound, Home/End, letter typeahead
- Escape closes and returns focus to the trigger
- Outside pointerdown and Tab close without stealing focus
- Four placements: bottom/top × start/end (logical, RTL-safe)
- Flips to the other edge when the panel would leave the viewport
- MenuGroup for labeled sections, MenuSeparator for dividers
- Destructive variant for irreversible actions
Usage
---
import { Menu, MenuItem, MenuSeparator } from "astro-ui";
---
<Menu id="row-actions" triggerLabel="Row actions">
<MenuItem shortcut="⌘E">Edit</MenuItem>
<MenuItem shortcut="⌘D">Duplicate</MenuItem>
<MenuSeparator/>
<MenuItem variant="destructive" shortcut="⌫">Delete</MenuItem>
</Menu>Demos
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | "bottom-start" | "bottom-end" | "top-start" | "top-end" | "bottom-end" | Corner of the trigger the panel aligns to. |
open | boolean | false | Render the panel open on first paint. |
disabled | boolean | false | Disable the trigger. |
bareTrigger | boolean | false | Drop the trigger's own border/background. |
triggerLabel | string | "Actions" | Accessible name for the trigger. |
class | string | — | Merged via cn(); Tailwind classes welcome. |
id | string | — | Element id. |
ariaLabel | string | — | Accessible name. |
Slots
| Slot | Description |
|---|---|
trigger | Trigger content; defaults to a ⋯ glyph. |
(default) | MenuItem, MenuGroup, and MenuSeparator children. |
Accessibility
Implemented accessibility features — semantics, ARIA wiring, and keyboard behavior that ship with the component.
- Trigger is a real button with
aria-haspopup="menu"andaria-expanded - Panel is
role="menu"labelled by its trigger - Items are native buttons/links with
role="menuitem"andtabindex="-1" - Disabled items use
aria-disabledand are skipped by keyboard navigation - Shortcut hints are
aria-hidden— they mirror platform bindings, not app state
FAQ
How does MenuItem behave?
It renders `<a>` when `href` is set and `<button type="button">` otherwise, with `role="menuitem"` and `tabindex="-1"`. Props: `variant` (default/destructive), `href`, `disabled`, `shortcut`, `class`, `id`, plus an `icon` slot.
What about MenuGroup and MenuSeparator?
MenuGroup takes a `label` and renders `role="group"` labelled by that heading, so screen readers name the section. MenuSeparator is an `<hr role="separator">`, skipped by arrow-key navigation.
Do submenus work?
Not yet. Nested flyouts need a second positioning layer plus hover-intent timers; until then, split large menus into separate entry points so every action stays one level deep.
How do I run an action?
Attach a click listener to the MenuItem (or its `id`) in your own script. The menu closes and restores focus before your handler runs, so the action never loses its trigger.
Changelog
Version history mentioning Menu.
- Overlay (8): Dialog, AlertDialog, Sheet, Tooltip, Popover, HoverCard, Lightbox, Menu.
- Native-first interactive behavior: Dialog, AlertDialog, and Sheet on
<dialog>; Accordion and Collapsible on<details>; Tabs, Menu, Tree, and Combobox with WAI-ARIA roles and scoped keyboard scripts.