Context Menu Plugin
The Context Menu plugin adds a customizable right-click menu to your grid cells. Build anything from simple copy/paste actions to complex nested menus with conditional visibility, icons, and keyboard shortcuts.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/context-menu';Basic Usage
Section titled “Basic Usage”Define your menu items as an array—each item has an id, label, and action callback that receives context about the clicked cell (row data, column info, cell value, etc.). Add separators between groups of actions for visual clarity.
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'status', header: 'Status' } ], features: { contextMenu: { items: [ { id: 'copy', name: 'Copy Cell', action: (ctx) => navigator.clipboard.writeText(String(ctx.value)) }, { id: 'sep1', name: '', separator: true }, { id: 'delete', name: 'Delete Row', action: (ctx) => removeRow(ctx.rowIndex) }, ], }, },};import '@toolbox-web/grid-react/features/context-menu';import { DataGrid, useGrid } from '@toolbox-web/grid-react';import type { ContextMenuConfig } from '@toolbox-web/grid/plugins/context-menu';
function MyGrid({ data, onDelete }) { const { ref, element } = useGrid();
const contextMenu: ContextMenuConfig = { items: [ { id: 'copy', name: 'Copy Cell', action: (ctx) => navigator.clipboard.writeText(String(ctx.value)) }, { id: 'sep1', name: '', separator: true }, { id: 'delete', name: 'Delete Row', action: (ctx) => onDelete(ctx.rowIndex) }, ], };
return ( <DataGrid ref={ref} rows={data} columns={[ { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'status', header: 'Status' } ]} contextMenu={contextMenu} /> );}<script setup>import '@toolbox-web/grid-vue/features/context-menu';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';import { ref } from 'vue';
const data = [ { name: 'Alice', email: 'alice@example.com', status: 'active' }, { name: 'Bob', email: 'bob@example.com', status: 'inactive' },];
const gridRef = ref(null);
const contextMenuConfig = { items: [ { id: 'copy', name: 'Copy Cell', action: (ctx) => navigator.clipboard.writeText(String(ctx.value)) }, { id: 'sep1', name: '', separator: true }, { id: 'delete', name: 'Delete Row', action: (ctx) => console.log('Delete row:', ctx.rowIndex) }, ],};</script>
<template> <TbwGrid ref="gridRef" :rows="data" :context-menu="contextMenuConfig"> <TbwGridColumn field="name" header="Name" /> <TbwGridColumn field="email" header="Email" /> <TbwGridColumn field="status" header="Status" /> </TbwGrid></template>// Feature import - enables the [contextMenu] inputimport '@toolbox-web/grid-angular/features/context-menu';
import { Component, output } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';import type { ContextMenuItem } from '@toolbox-web/grid/plugins/context-menu';
@Component({ selector: 'app-my-grid', imports: [Grid], template: ` <tbw-grid #grid [rows]="rows" [columns]="columns" [contextMenu]="contextMenuConfig" style="height: 400px; display: block;"> </tbw-grid> `,})export class MyGridComponent { deleteRow = output<number>(); rows = [...];
columns: ColumnConfig[] = [ { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'status', header: 'Status' } ];
contextMenuConfig = { items: [ { id: 'copy', name: 'Copy Cell', action: (ctx: any) => navigator.clipboard.writeText(String(ctx.value)) }, { id: 'sep1', name: '', separator: true }, { id: 'delete', name: 'Delete Row', action: (ctx: any) => this.deleteRow.emit(ctx.rowIndex) }, ] as ContextMenuItem[], };}Default Context Menu
Section titled “Default Context Menu”<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/context-menu';
// Sample data for context menu demosconst sampleData = [ { id: 1, name: 'Alice', email: 'alice@example.com', status: 'active' }, { id: 2, name: 'Bob', email: 'bob@example.com', status: 'pending' }, { id: 3, name: 'Carol', email: 'carol@example.com', status: 'active' }, { id: 4, name: 'Dan', email: 'dan@example.com', status: 'inactive' },];const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'status', header: 'Status' },];// Default menu itemsconst defaultMenuItems = [ { id: 'copy', name: 'Copy Row', icon: '📋', shortcut: 'Ctrl+C', action: (params: ContextMenuParams) => console.log('Copy', params.row), }, { id: 'edit', name: 'Edit Row', icon: '✏️', action: (params: ContextMenuParams) => console.log('Edit', params.row) }, { id: 'sep1', name: '', separator: true }, { id: 'duplicate', name: 'Duplicate', icon: '📄', action: (params: ContextMenuParams) => console.log('Duplicate', params.row), }, { id: 'sep2', name: '', separator: true }, { id: 'delete', name: 'Delete', icon: '🗑️', cssClass: 'danger', action: (params: ContextMenuParams) => console.log('Delete', params.row), },];
const grid = queryGrid('tbw-grid');
grid.gridConfig = { columns, features: { contextMenu: { items: defaultMenuItems } }, }; grid.rows = sampleData;Right-click any cell to see the context menu.
With Submenus
Section titled “With Submenus”<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/context-menu';
// Sample data for context menu demosconst sampleData = [ { id: 1, name: 'Alice', email: 'alice@example.com', status: 'active' }, { id: 2, name: 'Bob', email: 'bob@example.com', status: 'pending' }, { id: 3, name: 'Carol', email: 'carol@example.com', status: 'active' }, { id: 4, name: 'Dan', email: 'dan@example.com', status: 'inactive' },];const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'status', header: 'Status' },];const grid = queryGrid('tbw-grid');
const menuItems = [ { id: 'view', name: 'View', icon: '👁️', action: () => console.log('View') }, { id: 'edit', name: 'Edit', icon: '✏️', action: () => console.log('Edit') }, { id: 'sep1', name: '', separator: true }, { id: 'export', name: 'Export', icon: '📤', subMenu: [ { id: 'csv', name: 'As CSV', action: () => console.log('Export CSV') }, { id: 'json', name: 'As JSON', action: () => console.log('Export JSON') }, { id: 'excel', name: 'As Excel', action: () => console.log('Export Excel') }, ], }, { id: 'share', name: 'Share', icon: '🔗', subMenu: [ { id: 'email', name: 'Email', icon: '📧', action: () => console.log('Share Email') }, { id: 'slack', name: 'Slack', icon: '💬', action: () => console.log('Share Slack') }, ], }, ];
grid.gridConfig = { columns, features: { contextMenu: { items: menuItems } }, }; grid.rows = sampleData;Nested menu items for complex actions.
Conditional Items
Section titled “Conditional Items”<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/context-menu';
// Sample data for context menu demosconst sampleData = [ { id: 1, name: 'Alice', email: 'alice@example.com', status: 'active' }, { id: 2, name: 'Bob', email: 'bob@example.com', status: 'pending' }, { id: 3, name: 'Carol', email: 'carol@example.com', status: 'active' }, { id: 4, name: 'Dan', email: 'dan@example.com', status: 'inactive' },];const columns = [ { field: 'id', header: 'ID', type: 'number' }, { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'status', header: 'Status' },];const grid = queryGrid('tbw-grid');
const menuItems = [ { id: 'activate', name: 'Activate', icon: '✅', disabled: (params) => (params.row as { status?: string })?.status === 'active', action: (p) => console.log('Activate', p.row), }, { id: 'deactivate', name: 'Deactivate', icon: '⏸️', disabled: (params) => (params.row as { status?: string })?.status !== 'active', action: (p) => console.log('Deactivate', p.row), }, { id: 'sep1', name: '', separator: true }, { id: 'delete', name: 'Delete', icon: '🗑️', cssClass: 'danger', action: (p) => console.log('Delete', p.row) }, ];
grid.gridConfig = { columns, features: { contextMenu: { items: menuItems } }, }; grid.rows = sampleData;Show/hide items based on context.
Plugin-Contributed Items
Section titled “Plugin-Contributed Items”Right-click any cell to see the context menu. Items from FilteringPlugin, VisibilityPlugin, and PinnedColumnsPlugin are automatically contributed alongside custom items.
<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/context-menu';import '@toolbox-web/grid/features/filtering';import '@toolbox-web/grid/features/pinned-columns';import '@toolbox-web/grid/features/visibility';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'id', header: 'ID', type: 'number', width: 80 }, { field: 'name', header: 'Name', sortable: true }, { field: 'email', header: 'Email', sortable: true }, { field: 'status', header: 'Status', sortable: true }, ], features: { filtering: true, visibility: true, pinnedColumns: true, contextMenu: { items: [ { id: 'copy-row', name: 'Copy Row', icon: '📋', action: (params) => { console.log('Copy row:', params.row); }, }, { id: 'highlight', name: 'Highlight Row', icon: '🔆', action: (params) => { const rowEl = params.rowElement; if (rowEl) { rowEl.style.background = 'light-dark(#fff3cd, #4a3f00)'; setTimeout(() => { rowEl.style.background = ''; }, 2000); } }, }, ], }, },};
grid.rows = [ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', status: 'active' }, { id: 2, name: 'Bob Smith', email: 'bob@example.com', status: 'pending' }, { id: 3, name: 'Carol Davis', email: 'carol@example.com', status: 'active' }, { id: 4, name: 'Dan Wilson', email: 'dan@example.com', status: 'inactive' }, { id: 5, name: 'Eve Brown', email: 'eve@example.com', status: 'active' }, { id: 6, name: 'Frank Miller', email: 'frank@example.com', status: 'pending' },];Plugins like FilteringPlugin, VisibilityPlugin, and PinnedColumnsPlugin can contribute their own items to the context menu automatically.
Configuration Options
Section titled “Configuration Options”| Option | Type | Default | Description |
|---|---|---|---|
items | ContextMenuItem[] | Copy + Export CSV | Menu items to display |
TypeScript Interfaces
Section titled “TypeScript Interfaces”ContextMenuItem— Menu item definition (id, label, icon, action, submenus, etc.)ContextMenuParams— Context passed to action callbacks (row, column, value, selection, etc.)ContextMenuConfig— Plugin configuration options
Selection Sync
Section titled “Selection Sync”When used with the SelectionPlugin (row mode), the context menu automatically syncs
the selection on right-click:
| Scenario | Behavior |
|---|---|
| Right-click a selected row | Multi-selection preserved |
| Right-click an unselected row | Selects only that row |
| No SelectionPlugin loaded | selectedRows = [rowIndex] |
| Right-click on header | selectedRows = [] |
This uses the plugin query system for loose coupling — no hard dependency on SelectionPlugin.
Conditional Items
Section titled “Conditional Items”features: { contextMenu: { items: [ { id: 'edit', name: 'Edit', hidden: (params) => params.column.editable !== true, }, { id: 'delete', name: 'Delete', disabled: (params) => params.row.locked === true, }, ], },},Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
context-menu-open | { params, items } | Menu opened |
Styling
Section titled “Styling”The context menu supports CSS custom properties for theming. Override these on tbw-grid or a parent container:
CSS Custom Properties
Section titled “CSS Custom Properties”| Property | Default | Description |
|---|---|---|
--tbw-context-menu-bg | var(--tbw-color-panel-bg) | Menu background |
--tbw-context-menu-fg | var(--tbw-color-fg) | Menu text color |
--tbw-context-menu-border | var(--tbw-color-border) | Menu border |
--tbw-context-menu-hover | var(--tbw-color-row-hover) | Item hover background |
--tbw-menu-min-width | 10rem | Minimum menu width |
--tbw-menu-item-padding | 0.375rem 0.75rem | Menu item padding |
--tbw-menu-item-gap | 0.5rem | Gap between icon and label |
--tbw-font-size-sm | 0.9285em | Menu font size |
--tbw-font-size-xs | 0.7857em | Shortcut text size |
--tbw-icon-size | 1em | Icon width |
Example
Section titled “Example”tbw-grid { /* Custom context menu styling */ --tbw-context-menu-bg: #2d2d2d; --tbw-context-menu-fg: #ffffff; --tbw-context-menu-border: #444444; --tbw-context-menu-hover: #3d3d3d; --tbw-menu-item-padding: 0.5rem 1rem;}CSS Classes
Section titled “CSS Classes”The menu uses these class names for advanced customization:
| Class | Element |
|---|---|
.tbw-context-menu | Menu container |
.tbw-context-menu-item | Menu item row |
.tbw-context-menu-item.disabled | Disabled item |
.tbw-context-menu-item.danger | Danger/delete action |
.tbw-context-menu-icon | Item icon container |
.tbw-context-menu-label | Item label text |
.tbw-context-menu-shortcut | Keyboard shortcut |
.tbw-context-menu-separator | Divider line |