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: the first enabled item is focused when the menu opens — right-click and long-press included, not just the keyboard path. On close, focus returns to the first focusable element inside your trigger slot (a button or link). If the trigger area holds no focusable element there is nothing to return focus to, so give a right-clickable region one whenever the action it exposes matters
  • 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 first focusable element in the trigger slot

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-subtle
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?

Voting requires cookies or local storage. What we store