Menu
Commands, links, checkboxes and submenus in a popover anchored to a trigger.
c2-menu
A menu of commands anchored to a trigger. The trigger is slotted into trigger (a c2-button, a c2-icon-button,
a plain <button>, …) or named by anchor; the rows are c2-menu-item children, with <hr> separators and
<h1>–<h6> group headings between them. The surface is a native popover on the top layer positioned by
c2-overlay, so it flips near the viewport edge, light-dismisses on Escape and outside clicks, and is never clipped
by an overflow: hidden parent.
Keyboard: the trigger opens the menu with Enter, Space or ArrowDown (ArrowUp opens it on the last row). Inside, Arrow Up/Down move between enabled rows, Home/End jump to the first/last, typing letters jumps to a matching row, Enter and Space activate, ArrowRight opens a submenu, ArrowLeft returns to its parent row, and Escape or Tab closes the menu and returns focus to the trigger.
Activating a row fires menu-select and closes the menu; keep-open on the menu (or on a single row) leaves it
open, which is what a list of checkbox rows wants.
The surface is as wide as its rows, never narrower than --c2-menu--min-width and, when the menu owns its trigger,
never narrower than that trigger either. fit-anchor pins it to exactly the trigger's width instead.
The host never writes its own attributes: while something is slotted into trigger it matches
c2-menu:state(has-trigger) (and takes space in the flow as an inline box); without one it is display: contents.
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Whether the menu is showing. Set it to open or close the menu from code. |
placement | 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end' | bottom-start | Preferred placement of the surface (floating-ui names). Submenus default to right-start. |
offset | number | undefined | undefined | Distance in pixels from the trigger along the placement direction. Defaults to the overlay's 8px gap. |
cross-offset | number | undefined | undefined | Shift in pixels perpendicular to the placement direction. Positive values move towards the aligned end. |
anchor | string | HTMLElement | undefined | undefined | Anchor element, or (attribute) the id of an element in the same tree. Defaults to the slotted trigger. |
fit-anchor | boolean | false | Make the surface exactly as wide as the anchor instead of as wide as its content. |
keep-open | boolean | false | Keep the menu open after a row is activated. Also available per row as keep-open. |
disabled | boolean | false | Ignores the trigger and never opens. |
aria-label | string | null | null | Accessible name of the surface. |
Slots
| Name | Description |
|---|---|
default | The rows: c2-menu-item elements, plus <hr> separators, <h1>–<h6> group headings and c2-menu-row lines of small choices. |
trigger | The element that opens the menu. Omit it and point anchor at an element elsewhere on the page. |
Events
| Name | Type | Description |
|---|---|---|
menu-select | CustomEvent<MenuSelectEventDetail> | Bubbles from the activated row: detail.value, detail.checked and detail.data of that row. |
toggle | ToggleEvent | Fired after the menu opens or closes; newState is open or closed. |
CSS custom properties
Grouped by semantic target and state from --c2-<component>__<target>__<state>--<property>. Click a group to collapse it; click a name to copy it.
| Name | Type | Default | Description |
|---|---|---|---|
--c2-menu--background | color | #ffffff | |
--c2-menu--min-width | pixel | 128px | Floor for the surface, which is otherwise as wide as its content (and never narrower than a slotted trigger). |
--c2-menu--max-height | pixel | 320px | The menu scrolls past this height. |
--c2-menu--border-top | border | 1px solid #e4e4e7 | |
--c2-menu--border-right | border | 1px solid #e4e4e7 | |
--c2-menu--border-bottom | border | 1px solid #e4e4e7 | |
--c2-menu--border-left | border | 1px solid #e4e4e7 | |
--c2-menu--border-top-left-radius | border-radius | 8px | |
--c2-menu--border-top-right-radius | border-radius | 8px | |
--c2-menu--border-bottom-left-radius | border-radius | 8px | |
--c2-menu--border-bottom-right-radius | border-radius | 8px | |
--c2-menu--box-shadow | box-shadow | 0 8px 24px rgba(24, 24, 27, 0.08) | |
--c2-menu--padding-top | padding | 4px | |
--c2-menu--padding-right | padding | 4px | |
--c2-menu--padding-bottom | padding | 4px | |
--c2-menu--padding-left | padding | 4px | |
--c2-menu--gap | pixel | — | Space between the rows. |
--c2-menu__separator--color | color | #e4e4e7 | |
--c2-menu__separator--margin | margin | 4px 0 | |
--c2-menu__heading--color | color | #71717a | |
--c2-menu__heading--font-size | font-size | 11px | |
--c2-menu__heading--font-weight | font-weight | 600 | |
--c2-menu__heading--letter-spacing | letter-spacing | 0.06em | |
--c2-menu__heading--text-transform | text-transform | uppercase | |
--c2-menu__heading--padding-top | padding | 8px | |
--c2-menu__heading--padding-right | padding | 8px | |
--c2-menu__heading--padding-bottom | padding | 4px | |
--c2-menu__heading--padding-left | padding | 8px | |
c2-menu-item
A row of a c2-menu: a command, a link (href), a checkbox or a radio (type), or the parent of a nested menu
slotted into submenu. Activating a row fires menu-select and closes the menu unless the row is marked
keep-open. The default slot is the label, description a second muted line, prefix-icon / suffix-icon take an
inline SVG, a c2-feather-* icon or a c2-mat-icon, and shortcut holds a keyboard hint (plain text or a c2-kbd).
Rows never handle the keyboard themselves: the menu owns arrow keys, typeahead and the roving tabindex, and
sets reserveIndicator on every row while any sibling is checkable so the labels of plain and checkable rows line up
(a property, not reflected: the menu never writes attributes on its rows).
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
value | string | — | The value reported in menu-select. |
disabled | boolean | false | Dims the row and ignores clicks and keyboard. |
type | MenuItemType | item | item is a command, checkbox toggles, radio picks one row of a name group. |
checked | boolean | false | Checked state of a checkbox / radio row; shown as a check mark or a dot. |
name | string | undefined | — | Radio group name. Selecting a radio row unchecks the other rows of the same group in that menu. |
href | string | undefined | — | Makes the row a link; Enter and a click navigate, and menu-select still fires. |
target | string | undefined | — | Browsing context used when href is set, such as _blank. |
destructive | boolean | false | Paints the row in the destructive colour, for delete-style commands. |
keep-open | boolean | false | Keeps the menu open after this row is activated, e.g. for a checkbox row. |
label | string | undefined | — | Label used for typeahead and as the default slot's fallback content. |
expanded | boolean | false | Set by the parent menu while this row's submenu is open. Announced as aria-expanded. |
reserve-indicator | boolean | false | Private: set by the parent menu on every row while any sibling is checkable, so the labels line up. |
Slots
| Name | Description |
|---|---|
default | The row label. Falls back to label, then value, when empty. |
description | Secondary line under the label (muted, smaller). |
prefix-icon | Icon shown before the label. |
suffix-icon | Icon shown after the label; replaced by the chevron when the row has a submenu. |
shortcut | Keyboard hint, aligned to the end of the row. |
submenu | A nested c2-menu opened by hover, click, Enter or ArrowRight. |
Events
| Name | Type | Description |
|---|---|---|
menu-select | CustomEvent<MenuSelectEventDetail> | Fired after the row is activated by a click, Enter or Space. Cancelable; a row with a submenu never fires it. |
checked-change | CustomEvent<{ checked: boolean; value: string }> | Fired after the checked state of a checkbox / radio row changes. |
CSS custom properties
Grouped by semantic target and state from --c2-<component>__<target>__<state>--<property>. Click a group to collapse it; click a name to copy it.
| Name | Type | Default | Description |
|---|---|---|---|
--c2-menu-item--min-height | pixel | 32px | |
--c2-menu-item--gap | pixel | 8px | Space between the indicator, icons, label and shortcut. |
--c2-menu-item--border-top-left-radius | border-radius | 6px | |
--c2-menu-item--border-top-right-radius | border-radius | 6px | |
--c2-menu-item--border-bottom-left-radius | border-radius | 6px | |
--c2-menu-item--border-bottom-right-radius | border-radius | 6px | |
--c2-menu-item--padding-top | padding | 6px | |
--c2-menu-item--padding-right | padding | 8px | |
--c2-menu-item--padding-bottom | padding | 6px | |
--c2-menu-item--padding-left | padding | 8px | |
--c2-menu-item--font-size | font-size | 14px | |
--c2-menu-item--font-weight | font-weight | — | |
--c2-menu-item--font-style | font-style | — | |
--c2-menu-item--font-family | font-family | — | |
--c2-menu-item--line-height | pixel | 20px | |
--c2-menu-item--color | color | #18181b | |
--c2-menu-item--background | color | transparent | |
--c2-menu-item__hover--background | color | #f4f4f5 | Also the highlight of the keyboard-focused row. |
--c2-menu-item__hover--color | color | — | Defaults to the row colour. |
--c2-menu-item__description--color | color | #71717a | |
--c2-menu-item__description--font-size | font-size | 12px | |
--c2-menu-item__description--line-height | pixel | 16px | |
--c2-menu-item__description--margin-top | pixel | 1px | |
--c2-menu-item__shortcut--color | color | #52525b | |
--c2-menu-item__shortcut--font-size | font-size | 11px | |
--c2-menu-item__shortcut--letter-spacing | letter-spacing | 0.08em | |
--c2-menu-item__icon--size | pixel | 16px | |
--c2-menu-item__icon--color | color | #71717a | |
--c2-menu-item__indicator--size | pixel | 16px | |
--c2-menu-item__indicator--color | color | — | The check mark / radio dot. Defaults to the row colour. |
--c2-menu-item__destructive--color | color | #dc2626 | Label and icon colour of a destructive row. |
--c2-menu-item__destructive__hover--background | color | #fef2f2 | |
--c2-menu-item__focus--outline | outline | none | A ring around the keyboard-focused row, drawn on top of its highlight. |
--c2-menu-item__focus--outline-offset | pixel | -2px | |
--c2-menu-item__disabled--opacity | opacity | 0.38 | |
--c2-menu-item__compact--size | pixel | 28px | Width and height of a choice in a c2-menu-row. |
--c2-menu-item__compact--border-radius | border-radius | 6px | |
--c2-menu-item__compact__icon--size | pixel | 18px | Size of the icon of a choice in a c2-menu-row. |
--c2-menu-item__compact__checked--outline | outline | 2px solid #18181b | The ring round the checked choice of a c2-menu-row. |
--c2-menu-item__compact__checked--outline-offset | pixel | -2px | |
--c2-menu-item__compact__checked--background | color | transparent | |
c2-menu-row
A row of small choices inside a c2-menu: colour swatches, shapes, icons. Its c2-menu-item children sit side by
side and show only their prefix-icon; the label (label, or the slotted text) becomes their accessible name, and a
checked one is ringed instead of ticked.
The row is one line, never wrapped, and the menu treats it so: ArrowLeft and ArrowRight move along it, ArrowUp and ArrowDown leave it for the
line above or below. Its items take part in the menu's radio groups, typeahead and menu-select like any row, and do
not reserve the check-mark column of the menu's other rows.
Slots
| Name | Description |
|---|---|
default | The c2-menu-item choices, each with its look in prefix-icon. |
CSS custom properties
Grouped by semantic target and state from --c2-<component>__<target>__<state>--<property>. Click a group to collapse it; click a name to copy it.
| Name | Type | Default | Description |
|---|---|---|---|
--c2-menu-row--gap | pixel | 4px | Space between two choices. |
--c2-menu-row--padding | padding | 2px 4px 6px | Space around the choices. |