Skip to main content
WireKit
Copy for LLM

Context Menu

The <x-wirekit::context-menu> component creates a right-click triggered dropdown menu. It follows the same keyboard navigation and ARIA patterns as Dropdown but uses the browser's contextmenu event as its trigger mechanism.

On touch devices — which have no right-click — the menu also opens on a long-press (touch-and-hold for half a second) anywhere on the trigger area. A scroll or drag gesture cancels the press, so the menu never fights with scrolling.

Usage

Right-Click Context Menu
Right-click this area

Basic Setup

<x-wirekit::context-menu>
    <x-slot:trigger>
        <div class="p-8 border-2 border-dashed rounded-lg">
            Right-click here
        </div>
    </x-slot:trigger>

    <x-wirekit::context-menu.item icon="edit" wire:click="edit({{ $item->id }})">
        Edit
    </x-wirekit::context-menu.item>
    <x-wirekit::context-menu.item icon="eye" href="/items/{{ $item->id }}">
        View Details
    </x-wirekit::context-menu.item>
    <x-wirekit::context-menu.separator />
    <x-wirekit::context-menu.item icon="trash" :danger="true" wire:click="delete({{ $item->id }})">
        Delete
    </x-wirekit::context-menu.item>
</x-wirekit::context-menu>

Icons

Items can display icons using WireKit's icon alias system:

Menu Items with Icons
Right-click for menu with icons
<x-wirekit::context-menu.item icon="edit">Edit</x-wirekit::context-menu.item>
<x-wirekit::context-menu.item icon="download">Download</x-wirekit::context-menu.item>
<x-wirekit::context-menu.item icon="trash" :danger="true">Delete</x-wirekit::context-menu.item>

Icons require the Icon system to be configured with a preset and the blade-icons package installed.

Danger Items

Use the danger prop for destructive actions. These items use the danger color tokens and signal caution to the user:

Danger Items
Right-click for destructive actions
<x-wirekit::context-menu.item :danger="true" icon="trash" wire:click="delete">
    Delete permanently
</x-wirekit::context-menu.item>

Disabled Items

Disabled Items
Right-click for partially disabled menu
<x-wirekit::context-menu.item :disabled="true" icon="download">
    Export (no permission)
</x-wirekit::context-menu.item>

Disabled items are visually muted, not focusable via keyboard navigation, and have aria-disabled="true".

On Table Rows

A common pattern is attaching a context menu to each record in a data list. Right-click any row to open the menu — only one context menu is ever visible at a time, so opening a new one automatically closes the previous one:

Context menu on table rows
Name
Email
Role
Ada Lovelace
Admin
Alan Turing
Editor
Grace Hopper
Viewer
@foreach($users as $user)
    <x-wirekit::context-menu>
        <x-slot:trigger>
            <div class="grid grid-cols-3 px-3 py-2">
                <div>{{ $user->name }}</div>
                <div>{{ $user->email }}</div>
                <div>{{ $user->role }}</div>
            </div>
        </x-slot:trigger>
        <x-wirekit::context-menu.item icon="eye" href="/users/{{ $user->id }}">
            View profile
        </x-wirekit::context-menu.item>
        <x-wirekit::context-menu.item icon="edit" wire:click="edit({{ $user->id }})">
            Edit user
        </x-wirekit::context-menu.item>
        <x-wirekit::context-menu.separator />
        <x-wirekit::context-menu.item icon="trash" :danger="true" wire:click="delete({{ $user->id }})">
            Delete
        </x-wirekit::context-menu.item>
    </x-wirekit::context-menu>
@endforeach

A <div>-based grid is used in the example above instead of a real <table> because <x-wirekit::context-menu> renders a wrapper element, and wrappers between <tbody> and <tr> are invalid HTML. If you need a true <table>, attach the right-click handler directly to each <tr> via Alpine: <tr x-data="wirekitContextMenu()" x-on:contextmenu="openAt($event)" x-on:click.outside="close()">.

Nest a <x-wirekit::context-menu.submenu> to add a flyout sub-menu. The parent item opens a child panel beside it (on hover, click, or ArrowRight); the child holds ordinary <x-wirekit::context-menu.item>s. Selecting a leaf item closes the whole menu.

Context menu with a submenu
Right-click anywhere in this card

Pass a label for plain parent text, or a <x-slot:label> for rich content. The parent carries aria-haspopup="menu" + aria-expanded; the child panel is its own role="menu". Submenus nest arbitrarily deep.

Behavior

  • Right-click (contextmenu event) opens the menu at the cursor position
  • Floating UI (bundled ~3.5 KB) positions the menu at the click coordinates with flip and shift
  • Click outside closes the menu automatically
  • Livewire SPA navigation (wire:navigate) automatically closes open menus
  • Transitions use scale + opacity for smooth open/close animation
  • The native browser context menu is prevented only inside the trigger area -- right-clicking elsewhere on the page behaves normally

Props

<x-wirekit::context-menu>

Prop Type Default Description
teleport bool true Teleport the panel to <body> so it escapes every ancestor stacking context, overflow: hidden, and contain: layout container — the same pattern Modal, Drawer, Alert-Dialog, Tooltip, and Command-Palette use. Set to false only when the panel must stay inside the component root (rare; almost always an anti-pattern).
scope string|null null Scoped personalization key

<x-wirekit::context-menu.item>

Prop Type Default Description
href string|null null URL (renders <a> instead of <button>)
danger bool false Destructive/danger styling
disabled bool false Disabled state
icon string|null null WireKit icon alias (e.g. 'trash', 'edit')
scope string|null null Scoped personalization key

<x-wirekit::context-menu.submenu>

Prop Type Default Description
label string|null null Parent item text (or pass a <x-slot:label> for rich content)
icon string|null null Optional leading icon (WireKit icon alias)
placement string 'right-start' Floating UI placement of the child panel
offset int 0 Distance in px between the parent item and the child panel
disabled bool false Disabled state
scope string|null null Scoped personalization key

Slots

Slot Purpose
trigger The area that responds to right-click
default Menu items and separators

Sub-Components

Component Purpose
context-menu.item A single menu entry (link or action)
context-menu.submenu A nested flyout sub-menu opened from a parent item
context-menu.separator Visual divider between item groups

Accessibility

  • Menu panel: role="menu", unique id (auto-generated)
  • Items: role="menuitem", tabindex="-1"
  • Disabled items: aria-disabled="true", skipped by keyboard navigation
  • Focus management: first item is focused on open, focus returns to trigger area on close
  • Click outside closes the menu
  • Escape closes the menu
  • Icons: aria-hidden="true" (decorative)
  • The native browser context menu is suppressed only within the trigger area

Context menus are a progressive enhancement. Every action available in the context menu must also be reachable through another UI path (button, dropdown menu, etc.) — keyboard and assistive-technology users never discover a right-click-only menu.

Keyboard Interaction

Key Action
Shift+F10 (or the platform context-menu key) on the trigger Open the context menu (keyboard equivalent of right-click)
ArrowUp / ArrowDown Move between items
Home / End Jump to first / last item
Enter / Space Activate the focused item
ArrowRight (on a submenu parent) Open the submenu and focus its first item
ArrowLeft / Escape (inside a submenu) Close the submenu and return focus to the parent item
Escape Close the menu and return focus to the trigger

Pitfalls

  • Don't use context-menu as a primary action surface. Right-click + long-press are discoverable to <30% of users (UX research consensus). Mirror every context-menu action in a visible button or <x-wirekit::dropdown> so non-mouse users can reach it.
  • Don't nest context-menus. Sub-menus are supported via nested context-menus but two-level depth is the comfortable maximum.

Design Tokens

Element Token
Menu background --color-wk-bg-elevated
Menu border --color-wk-border / --border-wk-width
Menu radius --radius-wk-lg
Menu shadow --shadow-wk-lg
Menu padding --padding-wk-x-sm
Item text --color-wk-text
Item hover bg --color-wk-bg-active
Item radius --radius-wk-md
Item padding --padding-wk-x-md / --padding-wk-y-sm
Danger text --color-wk-danger-text
Danger hover bg --color-wk-danger
Icon color --color-wk-text-muted
Separator color --color-wk-border-subtle
Font family --font-wk-sans
Font size --text-wk-md
Disabled opacity --opacity-wk-disabled

Personalization

Override defaults in config/wirekit.php:

'components' => [
    'context-menu' => [],
],

Scoped Personalization

<x-wirekit::context-menu scope="file-manager">
    ...
</x-wirekit::context-menu>
'personalizations' => [
    'context-menu' => [
        'file-manager' => [
            'base' => 'min-w-[14rem]',
        ],
    ],
],

Further Reading

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store