Menu
Menu opens a short list of actions from a button, anchored to it. A native popover gives the top layer and light dismiss, CSS anchor positioning places it, and a small init() module adds the arrow-key model and focus handling.
Public web API
A root wrapper, a Button trigger with commandfor and command="toggle-popover", and a popover list of items. Authors write the full ARIA markup; init() adds only behavior.
<div data-bitsy-menu data-bitsy-placement="end">
<button data-bitsy-button data-bitsy-menu-trigger type="button" id="project-actions"
commandfor="project-actions-menu" command="toggle-popover" aria-haspopup="menu" aria-expanded="false">
Actions
</button>
<div data-bitsy-menu-list popover id="project-actions-menu" role="menu" aria-labelledby="project-actions">
<button data-bitsy-menu-item role="menuitem" type="button">Rename</button>
<button data-bitsy-menu-item role="menuitem" type="button">
Duplicate <kbd data-bitsy-menu-shortcut>⌘D</kbd>
</button>
<a data-bitsy-menu-item role="menuitem" href="/projects/atlas/export">Export</a>
<button data-bitsy-menu-item role="menuitem" type="button" aria-disabled="true">Archive</button>
<hr data-bitsy-menu-separator role="separator">
<button data-bitsy-menu-item role="menuitem" type="button" data-bitsy-variant="danger">Delete</button>
</div>
</div>
<script type="module">
import { init } from "@bitsyui/bits/menu/init";
init();
</script> - The list is named by its trigger with
aria-labelledby. The trigger carriesaria-haspopup="menu". - Items are buttons for actions and links for actions that open a page. Disabled items use
aria-disabled="true", neverdisabled, so they stay focusable and announced. data-bitsy-placement="end"aligns the list to the trigger's end. It flips above or to the other side when there is no room.- Group related items with
role="group"and adata-bitsy-menu-label. Separate sets withdata-bitsy-menu-separator. - Do not use it for navigation links. It is for actions.
Try it
Focus a trigger and press Enter, Space, or Down to open it on the first item, or Up for the last. Use the arrow keys, Home, End, or a letter to move. Escape closes and returns focus. Tab closes and moves on.
An item can open a Dialog. The menu closes first, and the Dialog returns focus to the trigger when it closes.
Near the edge of the screen the list flips. Scroll so this trigger sits at the bottom of the window, then open it.
Native behavior
- The browser provides the top layer, light dismiss, Escape, and closing one menu when another opens. There is no z-index.
- Opening and closing need no script: the trigger uses
commandforandcommand="toggle-popover". init()adds the arrow-key model, Home and End, type-ahead, focus on open, hover focus, andaria-expandedsync. It is about 1.5 KB gzipped, marks each root withdata-bitsy-menu-ready, and returns a cleanup function.- Placement uses CSS anchor positioning. Where it or
anchor-scopeis missing,init()places the list. - Without
init()the menu still opens and dismisses, and every item is reachable with Tab. - Motion is a short fade, removed under
prefers-reduced-motion: reduce.
Foundation versus Bit
The foundation styles buttons, links, hr, and kbd but not [popover]. data-bitsy-menu adds the list surface, the item rows, groups, separators, placement, and motion. The trigger is a normal Button; Menu only adds data-bitsy-menu-trigger.