Components · Actions

Menu

Transient surfaces that hold actions or detail. Both hide things behind a click, which is fine for the secondary and never acceptable for the urgent.

Stable · v1.0 shadcn/ui WCAG 2.2 AA

Overview

A menu holds actions. A popover holds content. A split button is a primary action with a menu of alternatives attached, and a menu button is a menu with no default. All four share one property that governs everything below: they are closed most of the time.

UseForNever for
Menu buttonA list of secondary actions with no obvious default The primary action of a screen
Split buttonA clear default plus alternatives — "Export PDF" with other formats Actions of equal weight; that is a menu button
PopoverSupporting detail too long for a tooltip — a reference range, an explanation of a model's scope Anything needed to act; anything requiring a decision
Dialog— See Dialog when a decision must be made before continuing

Secondary only

Nothing behind a click is the only route to something a clinician needs. If it matters, it is on the screen.

Shallow

One level. Submenus cannot be held under interruption and are unreachable on touch with any reliability.

Yields

Positions itself clear of the alert region, closes on outside click and on Esc, and never blocks anything clinical.

Anatomy

Menu · split button · popover

Split button — a real default, alternatives attached.

Validated scope
Adult, 12-lead, non-paced rhythms. ST criteria are not validated in ventricular pacing.

Rules

What may be inside

A menu holds actions. The moment it holds anything a clinician has to fill in, decide between, or read carefully, it has become a dialog wearing a menu's clothes — and it has none of a dialog's protections.

No form controls in a menu

A text field, a date picker or a stepper inside a menu is offered by most systems and is refused here. A menu closes on an outside click, on Escape, and often on losing focus — all of which are correct for a list of actions and destroy half-entered input without warning. There is no confirmation step, no dirty-state prompt, and no room for the units, range or error text a clinical field requires.

If the reader must supply a value, that is a Dialog — which blocks deliberately, keeps what was typed, and can ask before discarding it.

Where it opens, and what it may cover

A menu is positioned relative to its trigger and flips to stay on screen. Two things it must never end up on top of.

Opening and closing

Why there is no Popover

This page was titled “Menu & popover” for a long time, its sidebar entry said so until today, and its accessibility section credited a Radix Popover primitive. There has never been a Popover in this package. That is worth deciding out loud rather than quietly correcting, because it is the most obvious control this system does not have.

The decision

A popover is a container defined by nothing except being transient. Every rule this system would want to give it depends on what is put inside it — so the rules live on the specific surfaces, and the generic container is not built.

What a popover is usually reached for, and what to use instead:

The needHere
A short explanation of a control Tooltip — supplementary only, never the sole carrier of anything, and dismissible without moving the pointer.
A set of actions on a thing This component. The actions are data rather than children, so an alarm or a finding cannot be placed inside the surface.
A question that must be answered before continuing Dialog — which blocks, and is required to offer a way out as well as a way on.
More detail about a value A panel in the layout — see Grid & layout. Clinical detail worth reading is worth a place that does not vanish when the pointer moves.
A form in a small floating surface Nothing. A form worth filling in is worth a region that survives an interruption, which is the one thing a transient surface guarantees it will not do.

The pattern underneath is the one this page and Select both end on: a transient surface accumulates features because it is convenient to put them there, and each addition costs a protection the surface was never designed to provide. A component whose only definition is “floating” has no grounds to refuse any of them.

What the component enforces, and what it only asks for

The surface holds its own keyboard and dismissal. What it refuses to hold is content, and that refusal is the point.

RuleHeld by How
Focus moves into the menu when it opensThe component The first enabled item is focused — written by hand after a gate found it missing entirely.
Asserted: Menu: focus moves to the first item on open
Arrow keys, Home and End traverse itThe component Including wrapping at both ends, and including unavailable items, which are announced as unavailable, described by their reason and do nothing when activated. They were native disabled and the keyboard stepped past them, so the reason reached pointer users only. Escape closes and returns focus to the trigger.
Asserted: Menu: arrows reach an unavailable item, which says why and does nothing
An alarm or a finding cannot be placed inside itThe component actions is data, not children. There is no slot to put a banner in.
Asserted: Menu: actions are data, so a banner cannot be put inside the surface
Destructive items sort last, below a separatorThe component Ordering is applied by the component, not by the order you pass.
Asserted: Menu: destructive actions are separated from routine ones
A disabled item says whyThe component note renders beside it, so a dead option is never a dead end.
Asserted: Menu: its subject and unavailable reasons are visible when open
Type-ahead moves focus and never selectsThe component A printable key moves focus to the next available item whose label starts with it, wrapping, and skipping unavailable items as the arrows do. Nothing is selected by typing. Driven in a browser.
Asserted: Menu: type-ahead moves focus to the matching item and selects nothing
The menu is described by what it acts onThe component The subject line sits beside the menu and is its description. It was a paragraph inside role="menu", which a screen reader in a menu does not read. Read from the accessibility tree.
Asserted: AX tree: the menu is described by what it acts on
The menu says what it acts onYou subject is a string. Nothing checks that it names the row the menu was opened from, and a menu acting on something other than what the reader believes is the failure this page is about.
The list stays short enough to be scannedYou Any number of actions is accepted. A menu that has grown a filter and a scrollbar is a panel that has not admitted it.
Why this is written down

A rule stated on a component page reads as a rule the component keeps. 5 of these 7 are; 2 are obligations this page was quietly handing to the reader. The component can stop a finding being put in a transient surface. It cannot stop the surface acting on the wrong subject.

Do's and don'ts

Do

The primary action is on the screen; the menu holds the secondary ones.

Don't

The action the screen exists for, one click down. Under time pressure it may as well not be there.

Do

Destructive item last, below a separator, visually distinct.

Don't

Permanent deletion between two routine actions, styled identically. One slip on a moving cart.

Do
Validated scope
Adult, 12-lead, non-paced.

Supporting explanation. The finding and its confidence are already on the screen behind it.

Don't
Confidence 0.94
Model v4.2 · trained to 2025-11

Reliance information behind a click. On a touch display, at the moment of the decision, it is not there.

Do

The menu names its subject. The right action reaching the wrong record costs one line to prevent.

Don't

Floating free of its context, and opened over the row it acts on. The clinician is choosing for a patient they can no longer see.

Do

Opens a dialog with units, permitted range and confirmation.

The ellipsis says a decision is coming. The value is entered somewhere that can hold it.

Don't

A menu closes on an outside click, on Escape, and on losing focus. All three are correct for actions and all three silently discard what was typed.

Do

Secondary text carries why it is unavailable. The item stays, so the reader knows the action exists.

Don't

A second line explaining what the action does means the label failed. Fix the label.

Accessibility

Outcomes of use

What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.

Clinical safety notes

Risk controls this page specifies

Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.

Implementation

$ npx shadcn@latest add https://md.notjustany.tech/r/menu.json
<Menu
  triggerLabel="More"
  subject="ECG acquired 14:26"      // what the actions will act on
  actions={[
    { id: "compare", label: "Compare with prior", onSelect: compare },
    { id: "reanalyse", label: "Re-analyse", disabled: true,
      note: "Needs a new ECG" },
    { id: "discard", label: "Discard draft report", onSelect: discard,
      destructive: true },
  ]}
/>

// 'actions' is data, not children, so an alarm or a finding cannot be placed
// inside the surface. Destructive items sort last.
NotJustAnyMed.Tech Design System · Menu · v1.0 · draft for review
Covers Popover, Menu, Menu button and Split button. AcuteLine is a fictional reference application.