Context Menu

Displays a menu to the user triggered by a right-click with a list of actions or functions.

Requires JavaScript

Installation

Add the component to your project:

rails generate shadcn:component context_menu

Usage

<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click here
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_label { "Actions" } %>
    <% content.with_separator %>
    <% content.with_item(href: "#") { "Back" } %>
    <% content.with_item(href: "#", disabled: true) { "Forward" } %>
    <% content.with_item(href: "#") { "Reload" } %>
    <% content.with_separator %>
    <% content.with_item(href: "#") { "Save As..." } %>
    <% content.with_item(href: "#") { "Print" } %>
  <% end %>
<% end %>

Examples

Basic

<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_item(href: "#") { "Edit" } %>
    <% content.with_item(href: "#") { "Duplicate" } %>
    <% content.with_item(href: "#") { "Archive" } %>
  <% end %>
<% end %>

With Icons

Add icons to menu items for better visual clarity.

<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_item(href: "#") do %>
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
      Edit
    <% end %>
    <% content.with_item(href: "#") do %>
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
      Copy
    <% end %>
    <% content.with_item(href: "#") do %>
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
      Delete
    <% end %>
  <% end %>
<% end %>

With Separators and Labels

Use separators and labels to organize menu items into logical groups.

Right click
<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_label { "Edit" } %>
    <% content.with_separator %>
    <% content.with_item(href: "#") { "Cut" } %>
    <% content.with_item(href: "#") { "Copy" } %>
    <% content.with_item(href: "#") { "Paste" } %>
    <% content.with_separator %>
    <% content.with_label { "View" } %>
    <% content.with_separator %>
    <% content.with_item(href: "#") { "Zoom In" } %>
    <% content.with_item(href: "#") { "Zoom Out" } %>
  <% end %>
<% end %>

With Keyboard Shortcuts

Display keyboard shortcuts to help users discover shortcuts.

<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_item(href: "#") do |item| %>
      Cut
      <% item.with_shortcut { "⌘X" } %>
    <% end %>
    <% content.with_item(href: "#") do |item| %>
      Copy
      <% item.with_shortcut { "⌘C" } %>
    <% end %>
    <% content.with_item(href: "#") do |item| %>
      Paste
      <% item.with_shortcut { "⌘V" } %>
    <% end %>
    <% content.with_separator %>
    <% content.with_item(href: "#") do |item| %>
      Select All
      <% item.with_shortcut { "⌘A" } %>
    <% end %>
  <% end %>
<% end %>

Destructive Items

Use the destructive variant for dangerous actions.

<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_item(href: "#") { "View Details" } %>
    <% content.with_item(href: "#") { "Edit" } %>
    <% content.with_separator %>
    <% content.with_item(variant: :destructive, href: "#") { "Delete" } %>
  <% end %>
<% end %>

Disabled Items

Disable menu items when an action is unavailable.

<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_item(href: "#") { "Copy" } %>
    <% content.with_item(href: "#", disabled: true) { "Paste (clipboard empty)" } %>
    <% content.with_item(href: "#") { "Select All" } %>
  <% end %>
<% end %>

With Checkbox Items

Use checkbox items for toggleable options within the context menu.

Right click
<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_label(inset: true) { "Appearance" } %>
    <% content.with_separator %>
    <% content.with_checkbox_item(checked: true) { "Show Toolbar" } %>
    <% content.with_checkbox_item(checked: false) { "Show Sidebar" } %>
    <% content.with_checkbox_item(checked: true) { "Show Status Bar" } %>
    <% content.with_separator %>
    <% content.with_checkbox_item(disabled: true) { "Enable Notifications" } %>
  <% end %>
<% end %>

With Radio Items

Use radio groups for mutually exclusive options.

Right click
<%= render Shadcn::ContextMenuComponent.new do |menu| %>
  <% menu.with_trigger do %>
    <div class="flex h-[100px] w-[200px] items-center justify-center rounded-md border border-dashed text-sm">
      Right click
    </div>
  <% end %>
  <% menu.with_menu do |content| %>
    <% content.with_label(inset: true) { "Sort By" } %>
    <% content.with_separator %>
    <% content.with_radio_group(value: "date") do |group| %>
      <% group.with_item(value: "name") { "Name" } %>
      <% group.with_item(value: "date", checked: true) { "Date Modified" } %>
      <% group.with_item(value: "size") { "Size" } %>
      <% group.with_item(value: "type") { "Type" } %>
    <% end %>
    <% content.with_separator %>
    <% content.with_label(inset: true) { "Order" } %>
    <% content.with_separator %>
    <% content.with_radio_group(value: "asc") do |group| %>
      <% group.with_item(value: "asc", checked: true) { "Ascending" } %>
      <% group.with_item(value: "desc") { "Descending" } %>
    <% end %>
  <% end %>
<% end %>

API Reference

ContextMenuComponent

The main context menu wrapper component. Triggered by right-clicking on the trigger area.

Slots

Slot Description
with_trigger The area that responds to right-clicks
with_menu The context menu content container (yields ContextMenuContentComponent)

ContextMenuContentComponent Slots

Slot Props Description
with_item href:, variant:, disabled:, inset: Menu item (can be a link or div)
with_checkbox_item checked:, disabled: Toggleable checkbox menu item
with_radio_group value: Group of mutually exclusive radio items
with_label inset: Section label for grouping items
with_separator - Visual separator between menu sections

ContextMenuItemComponent

API Reference

Prop Type Default Description
href String nil Link URL (renders as <a> if provided, <div> otherwise)
variant Symbol :default Item variant (:default, :destructive)
disabled Boolean false Whether the item is disabled
inset Boolean false Whether to add left padding for icon alignment

ContextMenuItemComponent Slots

Slot Description
with_shortcut Keyboard shortcut hint displayed on the right

Stimulus Controller

This component requires JavaScript. The Stimulus controller shadcn--context-menu provides interactivity.

Manages context menu open/close state, positioning at mouse cursor, keyboard navigation, and item selection.

Installation

Add to your config/importmap.rb:

pin "shadcn-rails", to: "shadcn/index.js"

Then in your app/javascript/controllers/index.js:

import { Application } from "@hotwired/stimulus"
import { registerShadcnControllers } from "shadcn-rails"

const application = Application.start()
registerShadcnControllers(application)

Or import just this controller:

import ContextMenuController from "shadcn-rails/controllers/context_menu_controller"

application.register("shadcn--context-menu", ContextMenuController)

Targets

Name Description
trigger The area that responds to right-click events
content The context menu content container
item Individual menu items for keyboard navigation

Values

Name Type Default Description
open Boolean false Current open/closed state

Actions

Action Description
show Opens the context menu at mouse position
hide Closes the context menu
close Alias for hide
selectItem Handles item selection and closes menu

TypeScript

Type definitions are included. Import types as needed:

import type { ContextMenuController } from "shadcn-rails"

Implementing Keyboard Shortcuts

Note: The keyboard shortcuts displayed in context menu items (via with_shortcut) are visual hints only. They do not automatically execute when the keys are pressed. This matches the behavior of shadcn/ui's Context Menu.

To implement actual keyboard shortcuts, you'll need to add a global keydown listener. Here's an example using a Stimulus controller:

// app/javascript/controllers/keyboard_shortcuts_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    this.boundHandleKeydown = this.handleKeydown.bind(this)
    document.addEventListener("keydown", this.boundHandleKeydown)
  }

  disconnect() {
    document.removeEventListener("keydown", this.boundHandleKeydown)
  }

  handleKeydown(event) {
    // Check for Cmd (Mac) or Ctrl (Windows/Linux)
    const modifier = event.metaKey || event.ctrlKey

    if (modifier && event.key === "z") {
      event.preventDefault()
      this.undo()
    } else if (modifier && event.key === "c") {
      event.preventDefault()
      this.copy()
    } else if (modifier && event.key === "v") {
      event.preventDefault()
      this.paste()
    }
  }

  undo() { /* implement undo logic */ }
  copy() { /* implement copy logic */ }
  paste() { /* implement paste logic */ }
}

Then apply the controller to your page or application layout:

<body data-controller="keyboard-shortcuts">
  <!-- Your page content -->
</body>

Accessibility

  • Uses role="menu" for the context menu content
  • Sets role="menuitem" on each item
  • Supports full keyboard navigation:
    • Escape - Closes the menu
    • / - Navigate through menu items
    • Home / End - Jump to first/last item
    • Enter / Space - Select focused item
  • Disabled items use data-disabled attribute and are skipped in keyboard navigation
  • Automatically closes when clicking outside the context menu
  • Menu is positioned at mouse cursor location, adjusted to stay within viewport
  • Focus is automatically moved to the first menu item when opened