BladewindUI: Context Menu Component

Context Menu

Adds a right-click menu to any region or element. It shares the visual style of Dropmenu but opens at the pointer position and supports nested submenus, disabled items, and separators. Menus automatically adjust their placement to stay within the viewport, flipping away from edges when needed.

Right-click anywhere in this box
markup
<x-bladewind::context-menu name="basic-menu">
    <x-slot:region>
        <div class="rounded-lg border border-dashed ...">Right-click anywhere in this box</div>
    </x-slot:region>

    <x-bladewind::context-menu.item icon="pencil-square">Edit</x-bladewind::context-menu.item>
    <x-bladewind::context-menu.item icon="document-duplicate">Duplicate</x-bladewind::context-menu.item>
    <x-bladewind::context-menu.item divider="true" />
    <x-bladewind::context-menu.item icon="trash" tone="danger">Delete</x-bladewind::context-menu.item>
</x-bladewind::context-menu>

The region slot defines the area that opens the menu when right-clicked. The keyboard context-menu key works automatically because it triggers the same browser contextmenu event. All other children are treated as menu items and appear in the order they are defined.

Disabled items

Set disabled="true" on an item to grey it out and remove it from pointer and keyboard interaction entirely: it is skipped by arrow-key navigation and cannot be clicked or activated.

Right-click anywhere in this box
markup
<x-bladewind::context-menu name="disabled-menu">
    ...
    <x-bladewind::context-menu.item divider="true" />
    <x-bladewind::context-menu.item disabled icon="trash" tone="danger">Delete</x-bladewind::context-menu.item>
</x-bladewind::context-menu>

Tone

tone="danger" tints an item's label and icon red, for destructive actions like the Delete item above. The default is normal.

Give an item a submenu slot containing further x-bladewind::context-menu.item elements to turn it into a submenu trigger. A submenu opens on hover, click, or →, and can itself contain another submenu, nesting is unlimited. ← or Escape closes the deepest open submenu and returns focus to its parent item.

markup
<x-bladewind::context-menu name="submenu-example">
    <x-slot:region>
        ...
    <x-bladewind::context-menu.item icon="folder-plus">
            New
            <x-slot:submenu>
                <x-bladewind::context-menu.item icon="document">File
                    <x-slot:submenu>
                        <x-bladewind::context-menu.item icon="document">Word</x-bladewind::context-menu.item>
                        <x-bladewind::context-menu.item icon="document">Excel</x-bladewind::context-menu.item>
                    </x-slot:submenu>
                </x-bladewind::context-menu.item>
                <x-bladewind::context-menu.item icon="folder">Folder</x-bladewind::context-menu.item>
            </x-slot:submenu>
        </x-bladewind::context-menu.item>
        <x-bladewind::context-menu.item icon="pencil-square">Rename</x-bladewind::context-menu.item>
    </x-bladewind::context-menu>
</x-bladewind::context-menu>

Targeting an element elsewhere on the page

Wrapping an element in the component’s region slot works well for a single card or row, but can be inconvenient for larger areas such as the entire page or elements you do not want to restructure. The target attribute provides an alternative. Set it to a CSS selector or element ID, and the component will listen for right-clicks on that element instead of using a region slot. The component can then be placed anywhere on the page, commonly just before the closing tag, without requiring any changes to the target element’s markup.

Right-click anywhere in this box (targeted, not wrapped in x-slot:region)
markup
<body>
    <div class="..."
        id="drive-container">
        Right-click anywhere in this box (targeted, not wrapped in x-slot:region)
    </div>

    <x-bladewind::context-menu name="targetted"
        target="drive-container">
        <x-bladewind::context-menu.item icon="pencil-square">Edit</x-bladewind::context-menu.item>
        <x-bladewind::context-menu.item icon="document-duplicate">Duplicate</x-bladewind::context-menu.item>
        <x-bladewind::context-menu.item divider="true" />
        <x-bladewind::context-menu.item icon="trash" tone="danger">Delete</x-bladewind::context-menu.item>
    </x-bladewind::context-menu>
</body>

target accepts either a bare id or class name, like drive-container from the example above. You can also specify a CSS ID like #drive-container, or a full CSS selector such as .drive-row or [data-file-id="42"] if you need something more specific. When target is set, the region slot is ignored entirely, since the targeted element takes over that role.

File list example

A common use case is a file browser like Dropbox or Google Drive, where right-clicking any row in a list opens a menu for that specific row. The rows themselves are a natural fit for Listview, and pairing it with target keeps the list's own markup untouched: give each row an id, then define one x-bladewind::context-menu per row after the list, each one targeting its row's id. That also avoids nesting the context menu's own wrapper element inside the <ul>/<li> structure Listview relies on, which would otherwise break the rounded corners on the first and last row.

  • Q3-budget.xlsx
  • team-offsite.jpg
  • Client contracts


markup
<x-bladewind::card no-padding="true">
    <x-bladewind::listview compact="true">
        @foreach ($files as $file)
            <x-bladewind::listview.item id="file-row-{{ $file->id }}">
                <x-bladewind::icon name="{{ $file->icon }}" class="size-5"/>
                <div>{{ $file->name }}</div>
            </x-bladewind::listview.item>
        @endforeach
    </x-bladewind::listview>
</x-bladewind::card>

@foreach ($files as $file)
    <x-bladewind::context-menu :name="'fileRowMenu' . $file->id" :target="'file-row-' . $file->id">
        <x-bladewind::context-menu.item icon="arrow-down-tray" wire:click="download({{ $file->id }})">Download</x-bladewind::context-menu.item>
        <x-bladewind::context-menu.item icon="pencil-square" wire:click="rename({{ $file->id }})">Rename</x-bladewind::context-menu.item>
        <x-bladewind::context-menu.item icon="document-duplicate" wire:click="duplicate({{ $file->id }})">Make a copy</x-bladewind::context-menu.item>
        <x-bladewind::context-menu.item divider="true" />
        <x-bladewind::context-menu.item icon="trash" tone="danger" wire:click="delete({{ $file->id }})">Delete</x-bladewind::context-menu.item>
    </x-bladewind::context-menu>
@endforeach

Because $file->id is baked into the row's id, the menu's target, and the wire:click calls on its items, each row's menu acts only on that row's file, even though every row and every menu shares the same markup. If you are not using Livewire, pass the row's identifier the same way you would for any other per-row action, for example through a plain onclick handler or a form inside the item.

Keyboard support

↓ / ↑ Move focus to the next or previous enabled item.
→ Open the focused item's submenu, if it has one.
← Close the current submenu and refocus its parent item.
Enter / Space Activate the focused item, or open its submenu.
Home / End Jump to the first or last enabled item.
Escape Close the current submenu, or the whole menu if none is open.

Full List Of Attributes

Context Menu

Option Default Available Values
name auto-generated Uniquely identifies this instance in the DOM and its JavaScript.
disableNative true false lets the browser's own context menu show and disables this component entirely, useful for turning the feature off conditionally.
target (none) A CSS selector or bare element id for an element elsewhere on the page to open this menu on right-click, instead of the region slot.
padded true Padding inside the menu list.
class (blank) Additional CSS classes for the menu list.

Context Menu Item

Option Default Available Values
icon (blank) Any Heroicons name.
disabled false Greys the item out and removes it from pointer and keyboard interaction.
tone normal normal | danger
divider false Renders a separator line instead of an item; ignores every other prop.
submenu (none) A named slot of further items, turning this item into a submenu trigger.

Context Menu with all attributes defined

markup
<x-bladewind::context-menu
name="basicMenu"
disable-native="true"
padded="true"
class="ml-2">

Context Menu Item with all attributes defined

markup
<x-bladewind::context-menu.item
    icon="folder-plus"
    disabled="false"
    tone="danger"
    divider="false">
    New
    <x-slot:submenu>
        <x-bladewind::context-menu.item icon="document">File</x-bladewind::context-menu.item>
    </x-slot:submenu>
</x-bladewind::context-menu.item>
The source file for this component is available in resources > views > components > bladewind > context-menu > index.blade.php, resources > views > components > bladewind > context-menu > item.blade.php