These are the four token attributes on <html>. The whole page — and every live preview — re-resolves from them.
Primitives
36
Menu
A list of commands, anchored to whatever opened it. The third thing that hangs off place(), and the difference from the other two is what the reader does with it: a tooltip is read, a popover is worked in, a menu is fired from and then gone — so every row dismisses it. It is not a Select either: a select reports a value that stays chosen and is announced as such, a menu fires and reports nothing, which is why no row here is ever aria-selected and the list carries no tick. Focus is the other half of that difference. A select keeps focus on its trigger and points aria-activedescendant at the row from there; a menu moves focus into the list, and Tab closes it rather than cycling inside — a menu you cannot leave forwards is a trap, not a menu. Pass point and it opens at a pair of coordinates instead of beside the trigger, which is what a context menu is.
import { Button, Menu, type MenuItem } from '@ilomee/aura-react';const items: MenuItem[] = [ { value: 'open', label: 'Open' }, { value: 'rename', label: 'Rename', description: 'Give it another name' }, { value: 'duplicate', label: 'Duplicate' }, { value: 'archive', label: 'Archive', disabled: true }, { value: 'delete', label: 'Delete', danger: true, separatorBefore: true },];/** * Click the trigger, then use the arrow keys. Focus moves into the list, the cursor is virtual — * one focused element with `aria-activedescendant` pointing at the row — and typing a letter jumps * to the row that starts with it. Enter runs the row, Escape closes without running anything, and * Tab leaves the menu entirely rather than cycling inside it. * * The disabled row is stepped over by the arrows rather than parked on, and the destructive one is * ruled off from the rest. */export default function Demo() { return ( <Menu items={items} label="Actions" onSelect={(value) => console.log(value)}> <Button variant="secondary">Actions</Button> </Menu> );}
The panel is built with Renderer2 rather than a template: the content is data, not markup the caller wrote, so a component instantiated into the body would put its own host element where the other three emit a <div class="aura-popup">. The directive removes its own auraMenu attribute in ngOnInit, as auraPopover does. There is no per-row template — the row is a label and a description, and a template input for two strings is API nobody asked for.
Props
Plain markup has no props — the classes below are the whole API. The four packages do nothing more than set these same classes for you.
Name
Type
Default
Notes
items
MenuItem[] | null
—
The commands. value identifies one to the handler, label is what it says; description, disabled, danger, group and separatorBefore are the rest of a row. Nullable renders an empty list rather than throwing.
children
ReactElement
—
The control that opens it. Cloned, not wrapped. Optional: with point there is no trigger at all.
label
string
—
The menu’s accessible name. role="menu" without one is announced as an unlabelled group.
opentwo-way
boolean
—
defaultOpen
boolean
false
onOpenChangeevent
(open: boolean) => void
—
onSelectevent
(value: string) => void
—
The row that was run. The menu closes on its own — that is what makes it a menu.
point
{ x: number; y: number } | null
—
Open at these viewport coordinates instead of beside the trigger — clientX/clientY, not page offsets.
side
'top' | 'right' | 'bottom' | 'left'
'bottom'
align
'start' | 'center' | 'end'
'start'
emptyLabel
string
'No actions'
width
number | string
—
The panel’s width — pixels or any CSS length. Left out, it is as wide as its widest row, floored at 200px and capped at 320px. It is never the trigger’s width: a menu is routinely opened from something narrower than itself, and at a point there is no trigger to measure.
renderItem
(item, state) => ReactNode
—
Replaces a row’s contents — never its <li>.
Name
Type
Default
Notes
items
MenuItem[] | null
—
defaultslot
slot
—
The trigger. Exactly one element, cloned. Optional with point.
Handed the props to spread onto the control. Optional with point.
item
Snippet<[item, state]>
—
Replaces a row’s contents.
label
string
—
opentwo-way
boolean
—
onselectevent
(value: string) => void
—
point
{ x: number; y: number } | null
—
side
'top' | 'right' | 'bottom' | 'left'
'bottom'
align
'start' | 'center' | 'end'
'start'
emptyLabel
string
'No actions'
width
number | string
—
Name
Type
Default
Notes
auraMenu
AuraMenuItem[] | null
—
The commands, named after the selector so the binding reads on the trigger.
label
string
—
opentwo-way
boolean
—
openChangeevent
(open: boolean) => void
—
selectevent
(value: string) => void
—
point
{ x: number; y: number } | null
—
side
'top' | 'right' | 'bottom' | 'left'
'bottom'
align
'start' | 'center' | 'end'
'start'
emptyLabel
string
'No actions'
width
number | string
—
Emitted classes
Every framework emits the same classes — that is what makes the four libraries look identical. You can use them directly, without any component library.
Class
Role
Notes
.aura-popup
base
Required. A modifier does nothing without it.
.aura-menu
base
Required. A modifier does nothing without it.
.aura-listbox
base
Required. A modifier does nothing without it.
.aura-listbox__option
element
.aura-listbox__separator
element
Tokens and rules
components.css
layer
var(--z-dropdown) — under a modal, on purpose
width
its rows, floored at 200px and capped at 320px — or var(--menu-w)
height
min(320px, 60vh), then it scrolls — .aura-listbox’s own