<div data-controller="context-menu-preview"> <div class="inline-flex lui-context-menu" data-controller="context-menu lui--context-menu" data-context-menu-auto-close-value="false"> <div class="cursor-context-menu"> <button class="lui-button lui-button--size-default lui-button--core--secondary w-fit w-fit" data-controller="lui--button"> <span class="lui-button__text" data-lui--button-target="text"> Open Context Menu </span> </button> </div> <dialog class="lui-context-menu__dialog" style="margin: 0;" data-context-menu-target="menu" data-controller="menu" data-action="click@document->dropdown-popover#closeOnClickOutside keydown.up->menu#prev keydown.down->menu#next keydown.up->menu#preventScroll keydown.down->menu#preventScroll" > <div class="flex flex-col" role="menu"> <div class="flex flex-col" role="menu"> <div class="relative w-full focus-visible:outline-none" data-controller="dropdown-popover" data-dropdown-popover-nested-value="true" data-dropdown-popover-auto-close-value="false" data-dropdown-popover-hover-value="true" data-dropdown-popover-flip-class="translate-y-full"> <div class="focus-visible:outline-none" data-dropdown-popover-target="button" data-action="dropdown-popover#toggle" data-menu-target="item" role="menuitem"> <div class="lui-context-menu__item lui-context-menu__item--submenu" data-lui--context-menu-target="item"> <span class="lui-context-menu__item-text"> Size </span> <span class="lui-context-menu__icon"> <i class="fa-regular fa-chevron-right"></i> </span></div> </div> <dialog class="lui-context-menu__sub-dialog" data-controller="menu" data-dropdown-popover-target="menu" data-action="click@document->dropdown-popover#closeOnClickOutside keydown.up->menu#prev keydown.down->menu#next keydown.up->menu#preventScroll keydown.down->menu#preventScroll" > <div class="flex flex-col p-2" role="menu"> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Small </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Medium </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Large </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> </div> </dialog> </div> </div> <div class="flex flex-col" role="menu"> <div class="relative w-full focus-visible:outline-none" data-controller="dropdown-popover" data-dropdown-popover-nested-value="true" data-dropdown-popover-auto-close-value="false" data-dropdown-popover-hover-value="true" data-dropdown-popover-flip-class="translate-y-full"> <div class="focus-visible:outline-none" data-dropdown-popover-target="button" data-action="dropdown-popover#toggle" data-menu-target="item" role="menuitem"> <div class="lui-context-menu__item lui-context-menu__item--submenu" data-lui--context-menu-target="item"> <span class="lui-context-menu__item-text"> Color </span> <span class="lui-context-menu__icon"> <i class="fa-regular fa-chevron-right"></i> </span></div> </div> <dialog class="lui-context-menu__sub-dialog" data-controller="menu" data-dropdown-popover-target="menu" data-action="click@document->dropdown-popover#closeOnClickOutside keydown.up->menu#prev keydown.down->menu#next keydown.up->menu#preventScroll keydown.down->menu#preventScroll" > <div class="flex flex-col p-2" role="menu"> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Red </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Green </span> <span class="lui-context-menu__icon"> <i class="fa-regular fa-star"></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> <div data-lui--context-menu-target="item"> <div class="flex flex-col" role="menu"> <div class="relative w-full focus-visible:outline-none" data-controller="dropdown-popover" data-dropdown-popover-nested-value="true" data-dropdown-popover-auto-close-value="false" data-dropdown-popover-hover-value="true" data-dropdown-popover-flip-class="translate-y-full"> <div class="focus-visible:outline-none" data-dropdown-popover-target="button" data-action="dropdown-popover#toggle" data-menu-target="item" role="menuitem"> <div class="lui-context-menu__item lui-context-menu__item--submenu" data-lui--context-menu-target="item"> <span class="lui-context-menu__item-text"> Blue </span> <span class="lui-context-menu__icon"> <i class="fa-regular fa-chevron-right"></i> </span></div> </div> <dialog class="lui-context-menu__sub-dialog" data-controller="menu" data-dropdown-popover-target="menu" data-action="click@document->dropdown-popover#closeOnClickOutside keydown.up->menu#prev keydown.down->menu#next keydown.up->menu#preventScroll keydown.down->menu#preventScroll" > <div class="flex flex-col p-2" role="menu"> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Light Blue </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> <div data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <div> <div class="lui-context-menu__item " data-lui--context-menu-target="item" data-action="click->context-menu-preview#handleMenuItemClick"> <span class="lui-context-menu__item-text"> Dark Blue </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> </div> </dialog> </div> </div> </div> </div> </dialog> </div> </div> <div> <div class="lui-context-menu__item lui-context-menu__item--disabled " data-lui--context-menu-target="item"> <span class="lui-context-menu__item-text"> Storage </span> <span class="lui-context-menu__icon"> <i class=""></i> </span> <i class="lui-context-menu__checkmark fa-regular fa-check"></i> </div> </div> </div> </dialog> </div></div>ContextMenu
Description
Related components
| Used Components | Components where is Used |
|---|---|
| Label |
Usage rules
- ✅ Do
- ❌ Don't
<div data-controller="context-menu-preview"> <%= render LooposUi::ContextMenu.new do |context_menu| %> <%= context_menu.with_trigger do %> <%= render LooposUi::Button.new(text: "Open Context Menu", type: :secondary) %> <% end %> <%= context_menu.with_item(text: "Size") do |item| %> <%= item.with_sub_item(text: "Small", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick"}) %> <%= item.with_sub_item(text: "Medium", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick" }) %> <%= item.with_sub_item(text: "Large", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick" }) %> <% end %> <%= context_menu.with_item(text: "Color") do |item| %> <%= item.with_sub_item(text: "Red", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick" }) %> <%= item.with_sub_item(text: "Green", icon: "fa-regular fa-star", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick" }) %> <%= item.with_sub_item(text: "Blue") do |sub_item| %> <%= sub_item.with_sub_item(text: "Light Blue", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick" }) %> <%= sub_item.with_sub_item(text: "Dark Blue", data_attributes: { action: "click->context-menu-preview#handleMenuItemClick" }) %> <% end %> <% end %> <%= context_menu.with_item(text: "Storage", disabled: true) %> <% end %></div>No notes provided.
No params configured.
Context Menu
A context menu component that appears on left-click or long-press (mobile) and provides a list of actions or options. Supports nested submenus, keyboard navigation, and touch interactions.
Component Structure
The context menu consists of:
- ContextMenu: Main container component
- Item: Individual menu items that can have sub-items
- Sub-item: Nested menu items within a parent item
Item Options
| Property | Type | Default | Required | Description |
|---|---|---|---|---|
text |
String | - | ✓ | Display text for the menu item |
icon |
String | nil |
- | FontAwesome icon class (e.g., "fa-regular fa-chevron") |
disabled |
Boolean | false |
- | Whether the item is disabled and non-interactive |
data_attributes |
Hash | {} |
- | HTML data attributes to add to the item |
selected |
Boolean | false |
- | Whether the item is selected (shows checkmark icon) |
Sub-items
Items can contain sub-items to create nested menus:
<%= context_menu.with_item(text: "More Options", icon: "fa-regular fa-chevron-right") do |item| %> <%= item.with_sub_item(text: "Option 1", data_attributes: { action: "click->controller#action1" }) %> <%= item.with_sub_item(text: "Option 2", data_attributes: { action: "click->controller#action2" }) %><% end %>JavaScript Events
Listens to:
click- Opens context menu on the trigger elementtouchstart,touchend,touchmove,touchcancel- Handles mobile long-press (500ms)keydown- Handles Escape key to close menumenu-item-clicked- Closes menu when item is clickedmarkSelected(on the context menu root) – marks a given item as selected (see Selection API)
Emits:
menu-item-clicked- Fired when a menu item is clicked (bubbles up)
Selection API
The context menu provides a lightweight selection mechanism handled by the lui--context-menu Stimulus controller. The example below mirrors the preview implementation in test/dummy/app/javascript/controllers/context_menu_preview_controller.js.
- To select an item programmatically, dispatch a
markSelectedevent on the context menu root element with the clicked item indetail.item. - The controller will add
lui-context-menu__item--selectedto that item and remove it from its siblings scoped to the same menu.
Example dispatcher (Stimulus controller used in previews):
handleMenuItemClick(event) { const selectedItem = event.currentTarget this.contextMenu.dispatchEvent(new CustomEvent("markSelected", { detail: { item: selectedItem } }))}get contextMenu() { return this.element.querySelector(".lui-context-menu")}Notes:
- If you prefer, you can still manage selection via your own actions and classes; the selection API is optional.
- The context menu template already includes
data-controller="context-menu lui--context-menu"and item elements includedata-lui--context-menu-target="item".
Keyboard Navigation
- Arrow Up/Down: Navigate between menu items
- Arrow Right: Open submenu (if available)
- Arrow Left: Close submenu and return to parent
- Enter: Activate focused item
- Escape: Close entire context menu
- Letter keys: Quick search/filter items by text
Mobile Support
- Long press (500ms) on touch devices opens the context menu
- Touch movement during long press cancels the action if moved more than 10px
- Scroll locking prevents background scrolling when menu is open
Auto-close Behavior
By default, the context menu closes when:
- An item is clicked
- Escape key is pressed
- Clicking outside the menu
- Another context menu is opened
To prevent auto-closing on item click, set data-context-menu-auto-close-value="false" on the trigger element.
Examples
Basic Context Menu
<div data-controller="context-menu"> <div class="cursor-context-menu">Right-click me</div> <%= render LooposUi::ContextMenu.new do |context_menu| %> <%= context_menu.with_item(text: "Copy", data_attributes: { action: "click->clipboard#copy" }) %> <%= context_menu.with_item(text: "Paste", data_attributes: { action: "click->clipboard#paste" }) %> <% end %></div>Context Menu with Submenus
<div data-controller="context-menu"> <div class="cursor-context-menu">Right-click for options</div> <%= render LooposUi::ContextMenu.new do |context_menu| %> <%= context_menu.with_item(text: "File", icon: "fa-regular fa-chevron-right") do |item| %> <%= item.with_sub_item(text: "New", data_attributes: { action: "click->file#new" }) %> <%= item.with_sub_item(text: "Open", data_attributes: { action: "click->file#open" }) %> <%= item.with_sub_item(text: "Save", data_attributes: { action: "click->file#save" }) %> <% end %> <%= context_menu.with_item(text: "Edit", icon: "fa-regular fa-chevron-right") do |item| %> <%= item.with_sub_item(text: "Cut", data_attributes: { action: "click->edit#cut" }) %> <%= item.with_sub_item(text: "Copy", data_attributes: { action: "click->edit#copy" }) %> <%= item.with_sub_item(text: "Paste", data_attributes: { action: "click->edit#paste" }) %> <% end %> <% end %></div>Context Menu with Selection State
<div data-controller="context-menu"> <div class="cursor-context-menu">Right-click for size options</div> <%= render LooposUi::ContextMenu.new do |context_menu| %> <%= context_menu.with_item(text: "Small", selected: true, data_attributes: { action: "click->size#select" }) %> <%= context_menu.with_item(text: "Medium", data_attributes: { action: "click->size#select" }) %> <%= context_menu.with_item(text: "Large", data_attributes: { action: "click->size#select" }) %> <% end %></div>