Menu
Menus display a list of contextual actions or options in a popup, triggered from a button, icon, or list item.
Overview
Menus are used to provide a list of options/actions to a user, and are typically anchored dynamically to a button or other similar trigger element and appear upon selection of that trigger element.
Menus can be used in a variety of ways, such as a simple list of options, a list of actions, or a hierarchical/cascading list of options. They appear in front of all other permanent UI elements. If they are in a position to be cut off by the browser or screen’s edge, they can appear to the left, right, or above the element that generates it. Menus do not hold a selection state.
Usage
forge-menu wraps a trigger element and opens a dropdown of options — icon-and-label entries, optionally grouped, set as a JS property rather than an attribute — through mode (click or cascade), placement, and dense. Reserve it for a short list of contextual actions or unrelated options; for a field where a user picks one value from a fixed set, use a select instead — the menu’s own selectedIndex/selectedValue are deprecated for exactly that reason.
Rules
Basic usage
Wrap a trigger element — typically a button — in forge-menu and set its options property to populate the dropdown.
A menu opened from a button trigger.
<forge-menu id="basic-menu">
<forge-button type="button" variant="raised" aria-atomic aria-live="assertive" aria-haspopup aria-label="Menu">
Menu
</forge-button>
</forge-menu>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconSave, tylIconEdit, tylIconDelete } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconSave, tylIconEdit, tylIconDelete]);
const menu = document.getElementById("basic-menu");
const options = [
{ label: "Save", value: "save", leadingIcon: "save", leadingIconType: "component" },
{ label: "Edit", value: "edit", leadingIcon: "edit", leadingIconType: "component" },
{ label: "", value: "", divider: true },
{ label: "Delete", value: "delete", leadingIcon: "delete", leadingIconType: "component" }
];
menu.options = options;
menu.addEventListener("forge-menu-select", (evt) => {
console.log("Selected option:", evt.detail.value);
});
</script>Add an icon to an option with icon (or leadingIcon/trailingIcon to be explicit about placement), plus a matching leadingIconType/trailingIconType set to "component" to render it as a real forge-icon element:
const options = [
{ label: "Edit", value: "edit", leadingIcon: "edit", leadingIconType: "component" },
{ label: "Delete", value: "delete", leadingIcon: "delete", leadingIconType: "component" },
];
Grouped options
Pass grouped entries in options to divide a longer list into labeled sections instead of one flat list.
A menu with its options divided into labeled sections.
<forge-menu id="grouped-menu">
<forge-button type="button" variant="raised" aria-atomic aria-live="assertive" aria-haspopup aria-label="Menu">
Menu
</forge-button>
</forge-menu>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconSortAscending, tylIconSortDescending, tylIconContentCopy, tylIconArchive, tylIconDelete } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconSortAscending, tylIconSortDescending, tylIconContentCopy, tylIconArchive, tylIconDelete]);
const menu = document.getElementById("grouped-menu");
const options = [
{
text: "Sort",
options: [
{ label: "Name (A-Z)", value: "name-asc", leadingIcon: "sort_ascending", leadingIconType: "component" },
{ label: "Date modified", value: "date-modified", leadingIcon: "sort_descending", leadingIconType: "component" }
]
},
{
text: "Actions",
options: [
{ label: "Duplicate", value: "duplicate", leadingIcon: "content_copy", leadingIconType: "component" },
{ label: "Archive", value: "archive", leadingIcon: "archive", leadingIconType: "component" },
{ label: "Delete", value: "delete", leadingIcon: "delete", leadingIconType: "component" }
]
}
];
menu.options = options;
menu.addEventListener("forge-menu-select", (evt) => {
console.log("Selected option:", evt.detail.value);
});
</script>Cascading
Set mode="cascade" and nest a sub-options array on an option to open a submenu instead of selecting it immediately.
A cascading menu opens a submenu instead of selecting immediately.
<forge-menu id="cascading-menu" mode="cascade">
<forge-button type="button" variant="raised" aria-atomic aria-live="assertive" aria-haspopup aria-label="Menu">
Menu
</forge-button>
</forge-menu>
<script type="module">
import { IconRegistry } from "@tylertech/forge/icon";
import { tylIconSave, tylIconEdit, tylIconFileDownload, tylIconDelete } from "@tylertech/tyler-icons";
IconRegistry.define([tylIconSave, tylIconEdit, tylIconFileDownload, tylIconDelete]);
const menu = document.getElementById("cascading-menu");
const options = [
{ label: "Save", value: "save", leadingIcon: "save", leadingIconType: "component" },
{
label: "Edit",
value: "edit",
leadingIcon: "edit",
leadingIconType: "component",
options: [
{ label: "Overwrite", value: "overwrite" },
{ label: "Save as new", value: "save-as-new" }
]
},
{
label: "Export",
value: "export",
leadingIcon: "file_download",
leadingIconType: "component",
options: [
{ label: "PDF", value: "pdf" },
{ label: "CSV", value: "csv" },
{ label: "Excel", value: "excel" }
]
},
{ label: "", value: "", divider: true },
{ label: "Delete", value: "delete", leadingIcon: "delete", leadingIconType: "component" }
];
menu.options = options;
menu.addEventListener("forge-menu-select", (evt) => {
console.log("Selected option:", evt.detail.value);
});
</script>Menus display a list of options or actions that users can select from a dropdown. Menus wrap button or list item elements to provide the trigger for displaying the menu options.
import "@tylertech/forge/menu";| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
open | open | boolean | false | Gets/sets the open state. |
options | — | Array<IMenuOption | IMenuOptionGroup> | MenuOptionFactory | [] | Gets/sets the array of options to display in the menu. |
selectedIndex | selected-index | number | — | Gets/sets the selected option to the index. Does not support cascading menus. |
selectedValue | selected-value | any | — | Gets/sets the value of the option to select. |
placement | placement | `${PositionPlacement}` | "bottom-start" | Gets/sets the menu placement (default is bottom-left). |
fallbackPlacements | fallback-placements | `${PositionPlacement}`[] | — | Gets/sets the fallback menu placement for overriding the default of any side. |
dense | dense | boolean | false | Gets/sets dense state of the list options used in the menu popup. |
iconClass | icon-class | string | — | Gets/sets the class name to use for option icons. |
persistSelection | — | boolean | — | Gets/sets whether selection of menu items is persisted. |
mode | mode | MenuMode | "click" | Gets/sets the mode that this menu is using. |
popupOffset | — | IOverlayOffset | — | Sets the position adjustment on the internal popup element. |
optionBuilder | — | MenuOptionBuilder | — | Sets the callback that will be executed for each option in the dropdown for producing custom option templates. |
popupElement | — | HTMLElement | undefined | — | Gets the currently active popup element when the dropdown is open. |
popupTarget | popup-target | string | null | null | Gets/sets the ID of the element to use as the popup anchor for positioning. When null or empty, the target element (button) is used for both interaction and positioning. This is useful for cases like forge-list-item > button where the menu should be attached to the button for listeners but positioned relative to the list item. |
popupClasses | popup-classes | string | string[] | — | Gets/sets the list of classes to apply to the popup element. |
popupHeaderBuilder | — | ListDropdownHeaderBuilder | — | Gets/sets the callback function for generating header content within the popup. |
popupFooterBuilder | — | ListDropdownFooterBuilder | — | Gets/sets the callback function for generating header content within the popup. |
syncPopupWidth | sync-popup-width | boolean | false | Gets/sets whether the popup width is synchronized with the popup target width. |
optionLimit | option-limit | number | 0 | Gets/sets the maximum number of options to display in the dropdown. |
observeScroll | observe-scroll | boolean | false | Controls the observation of scroll events on the dropdown. |
observeScrollThreshold | observe-scroll-threshold | number | 0 | The number of pixels from the bottom to trigger the scroll bottom event. Only applicable if `observeScroll` is true. |
constrainPopupWidth | constrain-popup-width | boolean | true | Gets/sets whether the popup width will be constrained to a max width of the viewport width (default: `100vw`). |
wrapOptionText | wrap-option-text | boolean | false | Gets/sets whether the options will wrap their text or not. This only applies if `constrainPopupWidth` is `true`, if there is an explicit width set via CSS. |
popoverPlacement | popover-placement | OverlayPlacement | 'bottom' | Gets/sets the placement of the popover. |
popoverOffset | popover-offset | IOverlayOffset | — | Gets/sets the offset of the popover. |
popoverFlip | popover-flip | OverlayFlipState | 'auto' | Gets/sets the flip state of the popover. |
popoverShift | popover-shift | OverlayShiftState | 'auto' | Gets/sets whether the popover should shift to fit within the viewport. |
popoverFallbackPlacements | popover-fallback-placements | PositionPlacement[] | null | — | Gets/sets the fallback placements of the popover. |
| Name | Type | Description |
|---|---|---|
forge-menu-select | CustomEvent<IMenuSelectEventData> | Dispatches when a menu option is selected. |
forge-menu-open | CustomEvent<void> | Dispatches when the menu is opened. |
forge-menu-close | CustomEvent<void> | Dispatches when the menu is closed. |
forge-menu-active-change | CustomEvent<IMenuActiveChangeEventData> | Dispatches when the active menu option changes. |
| Name | Returns | Description |
|---|---|---|
propagateKeyEvent(evt) | void | Force propagates the key event from another element to this component. |
activateFirstOption() | void | Activates the first option in the menu when open. |
Trigger and item selection
- The trigger element that opens and closes the menu should be reachable and activatable by keyboard, with a visible focus cue.
- Pressing Space or Enter while the trigger is focused should open the menu the same way a click would.
- Once open, each menu item should be selectable using the up and down arrow keys.
Keyboard shortcuts
| Menu open | Action |
|---|---|
| Space / Escape | Closes the menu. |
| Enter / Right arrow | Opens or closes the focused item’s child menu. |
| Left arrow | Closes the menu (cascade mode only). |
| Menu closed | Action |
|---|---|
| Space | Opens the menu. |
| Enter | Opens the menu and its focused item’s child menu. |
| Down arrow | Opens the menu and focuses the first item. |
Interactive elements in a menu option
Placing an interactive element inside a menu option needs the same forge-ignore handling described in nested interactive elements.