Skip to content
Nana UI

Navigation

Menu Button

A button that opens a menu of commands, with the menu keyboard contract rather than a listbox's.

View .md Download

A button that opens a menu. Use it for a list of commands — a profile menu, a row’s actions — where choosing a row does something.

Settings Billing Sign out

They look alike and behave differently, and picking the wrong one shows up as surprising keyboard behaviour rather than anything visible.

nana-menu-buttonnana-dropdown
Patternmenulistbox
Childrennana-menu-itemnana-option
Choosing a rowruns a commandselects a value and keeps it
Tabcloses it and moves oncloses it and moves on
Keeps a selectionnoyes

A SACCO or account switcher picks a value, so it is a dropdown. A profile menu runs commands, so it is a menu button.

In both, Tab leaves rather than cycling inside — that is the pattern for both, and neither traps focus. Move between rows with the arrow keys.

npm install @nana-tec/ui-components
import "@nana-tec/ui-components/menu-button";

Slot trigger to replace the label entirely — an avatar, an icon button, anything. The slotted content sits inside the real button, so it keeps the popup semantics.

⋯ Rename Delete

A trigger that shows only an avatar or an icon has no accessible name, and neither does one whose label is hidden at narrow widths. aria-label reaches the real button inside:

AB Nairobi Sacco Mombasa Sacco

Without a label, an aria-label or trigger text, the component warns in the console rather than shipping an unnamed control.

KeyResult
Enter / Space / ↓Opens onto the first row
↑Opens onto the last row
↑ / ↓Moves between rows, wrapping at the ends
Home / EndJumps to the first or last row
EscCloses it and returns focus to the button
TabCloses it and carries on to whatever follows

Disabled rows are skipped.

Properties

PropertyAttributeTypeDefaultDescription
ariaLabelaria-labelstring | nullnullNames the trigger when its content cannot. A trigger showing only an avatar or an icon — or one whose label is hidden at narrow widths — has no accessible name without this.
disableddisabledbooleanfalse
labellabelstring''Text on the trigger when no trigger slot is given.
openopenbooleanfalse
placementplacement'start' | 'end''start'Side the panel sits on when there is room.

Events

EventDescription
nana-openAfter the menu opens
nana-closeAfter the menu closes
nana-selectBubbled from a chosen item (detail.value)

Slots

SlotDescription
triggerCustom trigger content (falls back to the label text)
(default)nana-menu-item / nana-menu-label / <hr> children

CSS parts

PartDescription
::part(trigger)The button
::part(panel)The floating menu panel

CSS custom properties

PropertyDescription
--nana-menu-button-panel-min-widthNarrowest the panel may be
--nana-menu-button-zStacking order for the panel
EventTypeDescription
nana-openCustomEventThe menu opened.
nana-closeCustomEventThe menu closed.
nana-selectCustomEventBubbled from a chosen row; detail.value.

The trigger carries aria-haspopup="menu" and aria-expanded, and the panel is a labelled menu. Focus moves onto a real row rather than being tracked with aria-activedescendant, which is what separates a menu from a listbox.

  • Dropdown — choosing a value rather than running a command
  • Menu — the rows on their own, without a trigger
  • Menubar — several menus along a bar