HyMenuButton
Usage examples on this page are written for Lit / plain HTML (
<hy-menu-button>). The same component ships asHyMenuButtonin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A trigger + menu pair implementing the ARIA menu-button pattern. The trigger is any element slotted into trigger; the menu surface is the child hy-menu (rendered in the default slot) or an actions shorthand.
Trigger shapes
- Slotted trigger — put any element (hy-button, hy-icon-button, chip, avatar, …) into
slot="trigger". You own the label, icons, and styling. - Default trigger — when
labelis set andslot="trigger"is empty, the component renders an internalhy-button caret truncatewith the label text and a trailing chevron. Thetruncateflag means long labels clip with an ellipsis instead of overflowing the row, so the default trigger is safe to use as a "value picker" reflecting an unbounded current selection. Slotted triggers don't get this for free — settruncateon your ownhy-buttonif you need it.
Composition contract
Slotted hy-menu-item children always win over the actions array — same precedence as hy-menu-item's own label prop losing to the default slot. When both are set in dev mode, a single console warning fires per element.
ARIA wiring
The trigger receives aria-haspopup="menu" and aria-expanded; the default trigger also an aria-controls pointing at the internally managed menu id (a slotted trigger gets none — its id reference could not reach the menu in this element's shadow root). Clicks on the trigger toggle the menu; Escape from the menu returns focus to the trigger (delegated to hy-popover). A host aria-label forwards to the default trigger, naming the control when the visible label is a current value (e.g. a picker showing "solid") rather than what the control does.
Split button or menu button?
When one action is the usual choice and the menu holds its variations (Save, then Save as… and Save a copy), use hy-split-button: the main action stays one click. When no action is the default, use a menu button.
When to use
- A set of commands that would crowd the page if shown as buttons: an item's More actions.
- Secondary commands next to a visible main action.
Do
- Name the trigger by what the menu holds: "Export", "More actions".
- Put destructive commands last, set apart from the others.
- Group a long menu with
hy-menu-group.
When not to use
- Only one command: a plain
hy-button. - A command people use all the time: its own visible
hy-button, outside the menu. - Choosing a value a form reads:
hy-selectorhy-combobox. A menu announces commands, not options. - A set of destinations:
hy-sidebar,hy-tab-navorhy-link. - A trigger that cannot sit in the
triggerslot:hy-menuwithforset to the trigger's id.
Accessibility: your part
- When the trigger is an
hy-icon-button, give it alabeland anhy-tooltipwith the same name.
Examples
Slotted trigger with a divider and a danger action
<hy-menu-button>
<hy-button slot="trigger">
Actions
<hy-icon slot="suffix" name="chevron-down"></hy-icon>
</hy-button>
<hy-menu-item value="edit" label="Edit"></hy-menu-item>
<hy-menu-item value="duplicate" label="Duplicate"></hy-menu-item>
<hy-menu-item value="archive" label="Archive"></hy-menu-item>
<hy-divider></hy-divider>
<hy-menu-item value="delete" label="Delete" variant="danger"></hy-menu-item>
</hy-menu-button>Default trigger rendered from the label prop
<hy-menu-button label="Sort by" aria-label="Sort order">
<hy-menu-item value="name" label="Name"></hy-menu-item>
<hy-menu-item value="date" label="Date modified"></hy-menu-item>
<hy-menu-item value="size" label="Size"></hy-menu-item>
</hy-menu-button>
<script>
document.querySelector('hy-menu-button').addEventListener('select', (e) => {
console.log(e.detail.value);
});
</script>API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
open | open | boolean | false | Controlled open state. |
placement | placement | 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | 'right' | 'right-start' | 'right-end' | 'bottom-start' | Placement forwarded to the child menu. |
size | size | 'small' | 'medium' | 'medium' | Density variant forwarded to the child hy-menu (and through it to every menu item). Also sizes the default hy-button trigger (when label renders one). Does not re-size a slotted trigger — consumers size their own trigger element. |
label | label | string | '' | Label used by the default hy-button caret trigger. Ignored when slot="trigger" has content. |
width | width | string | '' | Fixed menu-panel width (any CSS length, e.g. '220px'). Forwarded to the child hy-menu. |
minWidth | min-width | string | '' | Minimum menu-panel width. Forwarded to the child hy-menu. |
fullWidth | full-width | boolean | false | Stretch the trigger to fill the host's inline size. Off by design; opt in for a full-width "select-style" dropdown trigger. The slotted (or default truncate) trigger grows to match. Distinct from width / minWidth, which size the dropped menu panel. |
actions | — | MenuAction[] | [] | Shorthand for rendering items from data. When set, items are rendered only if the default slot is empty. Slotted children always win. |
Events
| Event | Detail | Description |
|---|---|---|
select | — | Relayed from the child menu. Detail: MenuSelectDetail. |
change | — | Relayed from the child menu when a radio group changes. Detail: MenuChangeDetail — { selectedValue, name, reason }. |
show | — | Relayed from the child menu (does NOT bubble). |
hide | — | Relayed from the child menu (does NOT bubble). |
Slots
| Slot | Description |
|---|---|
trigger | The trigger element (button, icon-button, chip, avatar, …). When empty and label is set, an internal hy-button caret truncate is rendered (long labels clip with an ellipsis). |
default | hy-menu-item / hy-menu-item-checkbox / hy-menu-item-radio / hy-menu-group / hy-divider children. |
CSS Parts
| Part | Description |
|---|---|
trigger-wrapper | The wrapper element around the trigger slot. |
Methods
setTabIndex()
Projects a tabindex onto the trigger — called by a parent roving-tabindex controller (hy-toolbar) so the menu button joins its single tab stop.
focus()
Focuses the trigger.