molecule · stable · since 2026-09
Menu
Purpose
A menu of commands behind a button. The rule: does the entry lead somewhere, or does it do something here? A list of links is a Disclosure; commands are what this component holds, with what the ARIA pattern promises at the keyboard and one Tab stop for the group.
When to use, when not
Use it for the commands of a row or an object: duplicate, export, close, delete. Not for links (a Disclosure with a list of links), not for one command (a Button), not for a choice that is a value (Select).
Anatomy
- The button, with its text and a chevron, or three dots named by the label.
- The menu, named, aligned to the start or the end of the button.
- The entries, one command each; a destructive one in the red of non-conformity.
Props
| Name | Type | Default | What it does |
|---|---|---|---|
label | string | Names the button, when it shows only the dots, and the menu: "Actions pour camille@exemple.fr". | |
items | Item[] | The commands, in order. | |
onselect | (id: string) => void | Called with the chosen command's id, after the menu closed and the focus returned to the button. | |
children? | Snippet | The button's visible text. Without it the button shows three dots and is named by the label. | |
align? | 'start' | 'end' | 'start' | Which edge of the button the menu aligns with. |
class? | string | '' | Layout classes only. |
States
Three dots, a labelled button, and an unavailable command.
dots
The row menu: three dots named by the label, one Tab stop, the arrows inside. Open it with the down arrow too.
Light
Dark
320 px, light
320 px, dark
216 px, light
Forced colours, captured
labelled
A button with its text, the chevron saying it opens; the menu aligned to its end.
Light
Dark
320 px, light
320 px, dark
216 px, light
Forced colours, captured
withDisabled
An unavailable command stays visible and skips the arrows.
Light
Dark
320 px, light
320 px, dark
216 px, light
Forced colours, captured
Accessibility contract
- Role and name: a button with
aria-haspopup,aria-expandedand, while open,aria-controls; amenunamed bylabel;menuitementries inrole="none"items. - Keyboard, focus: Enter, Space and the down arrow open on the first entry, the up arrow on the last. Down, Up, Home and End move, wrapping; a letter moves to the next entry that starts with it; Escape closes and returns the focus; Tab closes and lets the focus go; a choice closes, returns the focus, then calls
onselect. - Pointer: a click outside closes; no veil, which would cost a second click.
- Live regions: none. The outcome of a command is the page’s, through LiveRegion.
Content rules
French. Entries are verbs with their object: “Exporter en HTML”, “Supprimer l’audit”. The label names what the commands act on: “Actions pour camille@exemple.fr“.
Do and don’t
- Do give
labela name that says which object, when several rows have a menu. - Do not put links in it; that is a Disclosure.
- Do not confirm inside the menu: a destructive entry opens a ConfirmDialog after the menu closed.
Examples
<Menu label="Actions pour {audit.name}" items={actions} onselect={(id) => run(id, audit)} /> Surfaces
- App: the row menu of the users list, whose commands submit the row’s forms.
- Extension: the audit list’s row commands, when the panel is on Svelte.
Related
IconButton, Disclosure, Button.
Decisions
- 2026-09-24: the entries are data and the outcome a callback with the id, so a page writes its commands once and the keyboard code lives here, where the app and the site each had a copy.
Criteria held by construction
- 7.1, Chaque script est-il, si nécessaire, compatible avec les technologies d'assistance ?
The button says it opens a menu with aria-haspopup and aria-expanded; the menu carries role menu with a name, the entries role menuitem in items with role none. - 7.3, Chaque script est-il contrôlable par le clavier et par tout dispositif de pointage ?
The arrows, Home, End and Escape are handled; the first letters find an entry; the focus returns to the button when the menu closes. - 12.8, Dans chaque page web, l'ordre de tabulation est-il cohérent ?
One entry is in the tab order at a time, so the group costs one Tab stop, and Tab leaves the menu without taking the focus back. - 12.10, Dans chaque page web, les raccourcis clavier n'utilisant qu'une seule touche sont-ils contrôlables par l'utilisateur ?
The first-letter search is a single-key shortcut bound to the open menu with the focus inside it, and to nothing else.
Tokens it reads
- Colour:
--rl-hover,--rl-ink,--rl-ink-soft,--rl-line-control,--rl-line-strong,--rl-nc,--rl-surface - Typography:
--rl-weight-bold,--rl-text-sm - Spacing:
--rl-space-1,--rl-space-2,--rl-space-3,--rl-control-sm - Layout:
--rl-radius,--rl-border,--rl-border-strong,--rl-z-drawer