HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramBuildingTraceBuildingRecallDeclaredServicesInsightsGet in touchSource on GitHub
Loading the reference
MenuView SourceEdit in SandboxView MarkdownCopy MarkdownOpen in ClaudeOpen in CursorA menu of commands that opens from a trigger.StableSince 0.1.0OverlaysUsageimport'@fusion.dev/helix/menu'import'@fusion.dev/helix-headless/menu'Preview
API ReferenceRoothelix-menuA menu of commands that opens from a trigger.
PropTypeDescriptionbackLabelstringDefault"Back"The text of the row that returns from a submenu.checkedValuesstring[]The values of the choice rows that are checked, anywhere in the menu, in
document order.closeOnSelectbooleanDefaulttrueWhether choosing a row closes the menu.contextbooleanDefaultfalseWhether the menu is the trigger's context menu: it opens where the reader
asks for one, with a right-click, a long press, Shift+F10 or the menu key.defaultOpenbooleanDefaultfalseWhether the element starts open. Ignored once open is written.emptyTextstringDefault"Nothing to show"The text shown and announced when there are no rows and none are loading.filterstringDefault""The text the rows are filtered by, which the filter field holds.filterablebooleanDefaultfalseWhether the menu shows a field above its rows that narrows them as the
reader types, with focus staying in the field, once
@fusion.dev/helix/menu/filter (or @fusion.dev/helix-headless/menu/filter)
is imported. Without that import it does nothing, so a menu that never
filters carries none of the filter's code, and a development build says
which import is missing.filterLabelstringDefault"Filter"The filter field's name and placeholder.filterMatch((text: string, filter: string) => boolean)Whether a row is kept by the filter: a function given the row's words and
the filter, trimmed, that returns true to keep it showing, as React
Aria's Autocomplete takes filter. See Listbox's filterMatch.labelstringDefault""Accessible name for the surface.loadingbooleanDefaultfalseWhether the rows are still loading.loadingTextstringDefault"Loading"The text shown and announced while the rows load.longPressLabelstringDefault"Long press or press Alt+Down to show the menu"The description a trigger gives under open-on="long-press", saying how
to open the menu. Translate it with the rest of your interface.noResultsTextstringDefault"No matches"The text shown when the filter keeps no rows.offsetnumberDefault4Space between the trigger and the surface, in pixels.openbooleanWhether the element is open.openOn"click"|"long-press"Default"click"What opens the menu from its trigger. long-press leaves a press to the
trigger's own action and opens the menu on a press held for half a second,
or on Alt+Down or Alt+Up.placement"bottom"|"bottom-end"|"bottom-start"|"left"|"left-end"|"left-start"|"right"|"right-end"|"right-start"|"top"|"top-end"|"top-start"Default"bottom-start"The side the menu opens on, when there is room.resultsLabelstringDefault"{count, plural, one {# result} other {# results}}"What a screen reader hears when the filter narrows the rows. {count} is
how many rows are left, written in the locale, and each language writes
the forms it has, as Listbox's results-label does.submenus"drill"|"flyout"Default"drill"How a submenu opens. drill shows it in place of the page showing, with
a way back. flyout opens it in a panel of its own beside its row.
ReadTypeDescriptionwhenSettledPromise<void>Resolves a frame after the first update, when transitions are switched on.
SlotDescription(default)The items.emptyWhat the menu shows when it has no items, for content richer than empty-text.loadingWhat the menu shows while its items load, for content richer than loading-text.no-resultsWhat the menu shows when the filter keeps no rows, for content richer than no-results-text.triggerThe control that opens the menu, or with context the area a context menu opens over.
EventTypeDescriptionchangeEventA reader's choice checked or unchecked rows. Read checkedValues for the rows that are checked now.helix-before-closeHelixBeforeCloseEventThe menu is about to close. Cancelable: cancel it to keep it open.helix-before-openHelixBeforeOpenEventThe menu is about to open. Cancelable: cancel it to keep it closed.helix-filterHelixFilterEventThe text in the filter field changed, with the new text in detail.value. Cancelable: cancel it to keep every row showing and narrow the rows yourself.helix-selectHelixSelectEventAn item was chosen. Cancelable: cancel it to keep the menu open.toggleToggleEventThe menu opened or closed. The event carries newState and oldState.
MethodTypeDescriptionhide() => voidCloses the menu and returns it to its first page.requestOpen(next: boolean) => booleanRequests that the element open or close, and returns whether the change
was applied.show() => voidOpens the menu.toggleOpen() => booleanRequests the opposite of the current state.
PartDescriptionbackThe control that returns from a submenu.back-markThe glyph on the back control of a submenu.emptyThe text shown when the menu has no items.headerThe bar above a submenu, holding the way back and its name.liveA live region that repeats the empty and loading text, so screen readers announce it when it changes. Visually hidden in the styled tier.loadingThe text shown while the items are loading.search-fieldWith filterable, the field that narrows the rows.surfaceThe floating panel the menu is drawn on. It is the menu itself, and with filterable it holds the filter field and then the menu.titleThe name of the submenu that is showing.
CSS PropertyDescription--floating-anchor-inline-sizeThe trigger's width, written on the panel while it is open, as in min-inline-size: var(--floating-anchor-inline-size). Read it; a value set here is written over.--floating-available-block-sizeThe room between the panel and the edge of the screen it opens toward, written on the panel while it is open. Read it; a value set here is written over.--menu-background-colorFill of the surface.--menu-border-colorColor of the panel's edge.--menu-border-widthWidth of the panel's edge. Defaults to --helix-border-floating, which is 0, so the panel stands out by its shadow unless a theme sets a border.--menu-max-block-sizeThe tallest the menu grows before it scrolls.--menu-min-inline-sizeThe narrowest the menu gets.--menu-shadowShadow under the panel. Defaults to --helix-shadow-floating, so the panel keeps a shadow over any content below it.
Custom stateDescriptiondrilledA submenu's page is showing, so content that is not a row can hide with the top page.enteringFrom the moment the panel shows until its entrance has finished.exitingFrom the moment the panel hides until its exit has finished.markA row on the current page has a checkbox or a link, so every row keeps the trailing column.openWhile the menu is open.placed-bottomWhile the open panel sits below its trigger.placed-leftWhile the open panel sits to the left of its trigger.placed-rightWhile the open panel sits to the right of its trigger.placed-topWhile the open panel sits above its trigger.settledSet one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.startA row on the current page has content in its leading column, so every row keeps that column.
Itemhelix-menu-itemOne row in a menu: a command, a choice, or a way into a submenu.
PropTypeDescriptioncheckablebooleanDefaultfalseWhether the row is one of a set of choices.checkedbooleanDefaultfalseWhether the row is checked.closeOnSelectbooleanWhether choosing the row closes the menu.disabledbooleanDefaultfalseWhether the row is disabled, so it cannot be chosen.downloadbooleanDefaultfalseWhether the link downloads the file when the row is chosen.exclusivebooleanDefaultfalseWhether only one row in its set can be checked at a time.hrefstringDefault""The URL the row links to. Setting it renders the row as a link.relstringDefault""The link's rel. Set it whenever you set target="_blank".shortcutstringDefault""The row's keyboard shortcut as a chord, such as mod+s, shown after the
label in the platform's glyphs and given to the row as
aria-keyshortcuts. mod is Command on an Apple keyboard and Control
elsewhere. Showing a shortcut does not bind it.targetstringDefault""Where the link opens, for rows that should open outside the current page.valuestringDefault""The row's value, sent in helix-select when the row is chosen.
ReadTypeDescriptionactivebooleanWhether the row has focus, which makes it the row the keyboard is on.hasSubmenubooleanWhether anything was slotted into submenu.selectedbooleanWhether the row has focus, the same as active.whenSettledPromise<void>Resolves a frame after the first update, when transitions are switched on.
SlotDescription(default)The label.checkReplaces the check mark of a checked row, such as with your own icon.chevronReplaces the caret of a row that opens a submenu.descriptionA line under the label that describes the row. It describes the row to a screen reader and is not part of its name.endWords after the label, such as a count or a detail, beside any shortcut. They describe the row and are not part of its name.startContent before the label, such as an icon.submenuThe rows of a submenu that this row opens.
EventTypeDescriptionhelix-before-toggleHelixBeforeToggleEventA choice row is about to be checked or unchecked, carrying the state asked for. Cancelable: cancel it to keep the row as it is.helix-selectHelixSelectEventThe row was chosen. Cancelable: cancel it to keep the menu open.
MethodTypeDescriptionclick() => voidPresses the row, as click() presses a native button: passed to the
control inside, which is what answers a click, so a script's click()
chooses the row, or follows its link, once.focus() => voidMoves focus onto the row itself.focusItem() => voidMoves focus to the row. Part of NavigableItem, which the shared keyboard
controller uses for the arrow keys.select() => voidChooses the row, the same as pressing it.
PartDescriptionendThe trailing column that holds the shortcut and the end slot. It is always rendered.itemThe row, which is an <a> where href is set.markThe trailing mark: a check mark when the row is checked, or a caret when it opens a submenu. A row never shows both, so they share one column.shortcutThe key drawn for shortcut.shortcut-kbdThe key inside it.shortcut-keyOne key of the shortcut's chord, such as Ctrl.shortcut-separatorThe + between two keys of the shortcut, where the platform writes one.startThe leading column that holds the start slot. It is always rendered.surfaceThe panel a submenu opens in beside the row, under the menu's submenus="flyout".
CSS PropertyDescription--menu-item-description-colorColor of the line under the label.--menu-item-detail-colorColor of the shortcut and the words in the end slot.--menu-item-endWidth of the trailing column, including the gap before it, which holds a check mark or a submenu caret.--menu-item-hover-colorFill under the pointer.--menu-item-startWidth of the leading column, including the gap after it, for a row with more than a mark. The menu sets it for each page, and 0 removes the column.
Custom stateDescriptioncheckedThe row is checked.disabledThe row is disabled.expandedUnder submenus="flyout", the row's submenu is open beside it.highlightedIn a menu with filterable, the arrow keys or the pointer reached the row while focus stays in the filter field.settledSet one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.submenu-markUnder submenus="flyout", a row in this row's submenu can be ticked or opens a submenu, so the submenu keeps its trailing column.submenu-startUnder submenus="flyout", a row in this row's submenu has content before its label, so the submenu keeps its leading column.
Sectionhelix-menu-sectionA group of rows in a menu, under a heading.
PropTypeDescriptionlabelstringDefault""The heading's words, when they are plain text. Content in the heading
slot replaces it.
ReadTypeDescriptionwhenSettledPromise<void>Resolves a frame after the first update, when transitions are switched on.
SlotDescription(default)The rows.headingThe heading, for content richer than label.
PartDescriptionheadingThe words that name the section.sectionThe group that holds the heading and the rows.
CSS PropertyDescription--menu-section-heading-colorColor of the heading.
Custom stateDescriptionsettledSet one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
Roothelix-headless-menuA menu of commands that opens from a trigger.
PropTypeDescriptionbackLabelstringDefault"Back"The text of the row that returns from a submenu.checkedValuesstring[]The values of the choice rows that are checked, anywhere in the menu, in
document order.closeOnSelectbooleanDefaulttrueWhether choosing a row closes the menu.contextbooleanDefaultfalseWhether the menu is the trigger's context menu: it opens where the reader
asks for one, with a right-click, a long press, Shift+F10 or the menu key.defaultOpenbooleanDefaultfalseWhether the element starts open. Ignored once open is written.emptyTextstringDefault"Nothing to show"The text shown and announced when there are no rows and none are loading.filterstringDefault""The text the rows are filtered by, which the filter field holds.filterablebooleanDefaultfalseWhether the menu shows a field above its rows that narrows them as the
reader types, with focus staying in the field, once
@fusion.dev/helix/menu/filter (or @fusion.dev/helix-headless/menu/filter)
is imported. Without that import it does nothing, so a menu that never
filters carries none of the filter's code, and a development build says
which import is missing.filterLabelstringDefault"Filter"The filter field's name and placeholder.filterMatch((text: string, filter: string) => boolean)Whether a row is kept by the filter: a function given the row's words and
the filter, trimmed, that returns true to keep it showing, as React
Aria's Autocomplete takes filter. See Listbox's filterMatch.labelstringDefault""Accessible name for the surface.loadingbooleanDefaultfalseWhether the rows are still loading.loadingTextstringDefault"Loading"The text shown and announced while the rows load.longPressLabelstringDefault"Long press or press Alt+Down to show the menu"The description a trigger gives under open-on="long-press", saying how
to open the menu. Translate it with the rest of your interface.noResultsTextstringDefault"No matches"The text shown when the filter keeps no rows.offsetnumberDefault4Space between the trigger and the surface, in pixels.openbooleanWhether the element is open.openOn"click"|"long-press"Default"click"What opens the menu from its trigger. long-press leaves a press to the
trigger's own action and opens the menu on a press held for half a second,
or on Alt+Down or Alt+Up.placement"bottom"|"bottom-end"|"bottom-start"|"left"|"left-end"|"left-start"|"right"|"right-end"|"right-start"|"top"|"top-end"|"top-start"Default"bottom-start"The side the menu opens on, when there is room.resultsLabelstringDefault"{count, plural, one {# result} other {# results}}"What a screen reader hears when the filter narrows the rows. {count} is
how many rows are left, written in the locale, and each language writes
the forms it has, as Listbox's results-label does.submenus"drill"|"flyout"Default"drill"How a submenu opens. drill shows it in place of the page showing, with
a way back. flyout opens it in a panel of its own beside its row.
ReadTypeDescriptionwhenSettledPromise<void>Resolves a frame after the first update, when transitions are switched on.
SlotDescription(default)The items.emptyWhat the menu shows when it has no items, for content richer than empty-text.loadingWhat the menu shows while its items load, for content richer than loading-text.no-resultsWhat the menu shows when the filter keeps no rows, for content richer than no-results-text.triggerThe control that opens the menu, or with context the area a context menu opens over.
EventTypeDescriptionchangeEventA reader's choice checked or unchecked rows. Read checkedValues for the rows that are checked now.helix-before-closeHelixBeforeCloseEventThe menu is about to close. Cancelable: cancel it to keep it open.helix-before-openHelixBeforeOpenEventThe menu is about to open. Cancelable: cancel it to keep it closed.helix-filterHelixFilterEventThe text in the filter field changed, with the new text in detail.value. Cancelable: cancel it to keep every row showing and narrow the rows yourself.helix-selectHelixSelectEventAn item was chosen. Cancelable: cancel it to keep the menu open.toggleToggleEventThe menu opened or closed. The event carries newState and oldState.
MethodTypeDescriptionhide() => voidCloses the menu and returns it to its first page.requestOpen(next: boolean) => booleanRequests that the element open or close, and returns whether the change
was applied.show() => voidOpens the menu.toggleOpen() => booleanRequests the opposite of the current state.
PartDescriptionbackThe control that returns from a submenu.back-markThe glyph on the back control of a submenu.emptyThe text shown when the menu has no items.headerThe bar above a submenu, holding the way back and its name.liveA live region that repeats the empty and loading text, so screen readers announce it when it changes. Visually hidden in the styled tier.loadingThe text shown while the items are loading.search-fieldWith filterable, the field that narrows the rows.surfaceThe floating panel the menu is drawn on. It is the menu itself, and with filterable it holds the filter field and then the menu.titleThe name of the submenu that is showing.
CSS PropertyDescription--floating-anchor-inline-sizeThe trigger's width, written on the panel while it is open, as in min-inline-size: var(--floating-anchor-inline-size). Read it; a value set here is written over.--floating-available-block-sizeThe room between the panel and the edge of the screen it opens toward, written on the panel while it is open. Read it; a value set here is written over.
Custom stateDescriptiondrilledA submenu's page is showing, so content that is not a row can hide with the top page.enteringFrom the moment the panel shows until its entrance has finished.exitingFrom the moment the panel hides until its exit has finished.markA row on the current page has a checkbox or a link, so every row keeps the trailing column.openWhile the menu is open.placed-bottomWhile the open panel sits below its trigger.placed-leftWhile the open panel sits to the left of its trigger.placed-rightWhile the open panel sits to the right of its trigger.placed-topWhile the open panel sits above its trigger.settledSet one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.startA row on the current page has content in its leading column, so every row keeps that column.
Itemhelix-headless-menu-itemOne row in a menu: a command, a choice, or a way into a submenu.
PropTypeDescriptioncheckablebooleanDefaultfalseWhether the row is one of a set of choices.checkedbooleanDefaultfalseWhether the row is checked.closeOnSelectbooleanWhether choosing the row closes the menu.disabledbooleanDefaultfalseWhether the row is disabled, so it cannot be chosen.downloadbooleanDefaultfalseWhether the link downloads the file when the row is chosen.exclusivebooleanDefaultfalseWhether only one row in its set can be checked at a time.hrefstringDefault""The URL the row links to. Setting it renders the row as a link.relstringDefault""The link's rel. Set it whenever you set target="_blank".shortcutstringDefault""The row's keyboard shortcut as a chord, such as mod+s, shown after the
label in the platform's glyphs and given to the row as
aria-keyshortcuts. mod is Command on an Apple keyboard and Control
elsewhere. Showing a shortcut does not bind it.targetstringDefault""Where the link opens, for rows that should open outside the current page.valuestringDefault""The row's value, sent in helix-select when the row is chosen.
ReadTypeDescriptionactivebooleanWhether the row has focus, which makes it the row the keyboard is on.hasSubmenubooleanWhether anything was slotted into submenu.selectedbooleanWhether the row has focus, the same as active.whenSettledPromise<void>Resolves a frame after the first update, when transitions are switched on.
SlotDescription(default)The label.checkReplaces the check mark of a checked row, such as with your own icon.chevronReplaces the caret of a row that opens a submenu.descriptionA line under the label that describes the row. It describes the row to a screen reader and is not part of its name.endWords after the label, such as a count or a detail, beside any shortcut. They describe the row and are not part of its name.startContent before the label, such as an icon.submenuThe rows of a submenu that this row opens.
EventTypeDescriptionhelix-before-toggleHelixBeforeToggleEventA choice row is about to be checked or unchecked, carrying the state asked for. Cancelable: cancel it to keep the row as it is.helix-selectHelixSelectEventThe row was chosen. Cancelable: cancel it to keep the menu open.
MethodTypeDescriptionclick() => voidPresses the row, as click() presses a native button: passed to the
control inside, which is what answers a click, so a script's click()
chooses the row, or follows its link, once.focus() => voidMoves focus onto the row itself.focusItem() => voidMoves focus to the row. Part of NavigableItem, which the shared keyboard
controller uses for the arrow keys.select() => voidChooses the row, the same as pressing it.
PartDescriptionendThe trailing column that holds the shortcut and the end slot. It is always rendered.itemThe row, which is an <a> where href is set.markThe trailing mark: a check mark when the row is checked, or a caret when it opens a submenu. A row never shows both, so they share one column.shortcutThe key drawn for shortcut.shortcut-kbdThe key inside it.shortcut-keyOne key of the shortcut's chord, such as Ctrl.shortcut-separatorThe + between two keys of the shortcut, where the platform writes one.startThe leading column that holds the start slot. It is always rendered.surfaceThe panel a submenu opens in beside the row, under the menu's submenus="flyout".
Custom stateDescriptioncheckedThe row is checked.disabledThe row is disabled.expandedUnder submenus="flyout", the row's submenu is open beside it.highlightedIn a menu with filterable, the arrow keys or the pointer reached the row while focus stays in the filter field.settledSet one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.submenu-markUnder submenus="flyout", a row in this row's submenu can be ticked or opens a submenu, so the submenu keeps its trailing column.submenu-startUnder submenus="flyout", a row in this row's submenu has content before its label, so the submenu keeps its leading column.
Sectionhelix-headless-menu-sectionA group of rows in a menu, under a heading.
PropTypeDescriptionlabelstringDefault""The heading's words, when they are plain text. Content in the heading
slot replaces it.
ReadTypeDescriptionwhenSettledPromise<void>Resolves a frame after the first update, when transitions are switched on.
SlotDescription(default)The rows.headingThe heading, for content richer than label.
PartDescriptionheadingThe words that name the section.sectionThe group that holds the heading and the rows.
Custom stateDescriptionsettledSet one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
Examples
NameDescriptionContextA context menu that opens where you right-click the field, or press Shift+F10 in it.EventsThe events a menu sends as it opens, as an item is chosen, and as it closes.FilterA menu with a filter field, which narrows its rows and its submenu's rows as you type.FlyoutA menu whose submenus open in a panel beside their rows.LinksRows that navigate, rendered as anchors.Long PressA button that runs its own action on a press, and opens a menu on a long press or Alt+Down.PlacementA menu that opens to the right with a larger offset, and a row that downloads a file.PreviewA menu with items, a submenu, and a separator.PreviewA menu with items, a submenu, and a separator.SectionsA menu whose rows are grouped under headings, with each command's keyboard shortcut.