Skip to content

Context Menu

ContextMenuManager provides a right-click context menu rendered as a DOM overlay. The menu supports keyboard navigation (arrow keys to move, Enter to select, Escape to close) and adapts its items based on click context — cell, header, row-number, or corner.

Live Demo
Right-click cells, headers, row numbers, or the corner to see context-specific menus with custom items.
Right-click any cell, header, or row number to see the context menu.
View source code
ContextMenuDemo.tsx
import { useRef, useEffect, useState } from 'react';
import { WitTable } from '@witqq/spreadsheet-react';
import type { WitTableRef } from '@witqq/spreadsheet-react';
import { DemoWrapper } from './DemoWrapper';
import { generateEmployees, employeeColumns } from './generate-data';
import { useSiteTheme } from './useSiteTheme';
const data = generateEmployees(50);
export function ContextMenuDemo() {
const { witTheme } = useSiteTheme();
const tableRef = useRef<WitTableRef>(null);
const [lastAction, setLastAction] = useState('Right-click any cell, header, or row number to see the context menu.');
useEffect(() => {
const engine = tableRef.current?.getInstance();
if (!engine) return;
const cm = engine.getContextMenuManager();
if (!cm) return;
cm.registerItem({
id: 'highlight-row',
label: '🟡 Highlight Row',
contexts: ['cell', 'row-number'],
action: (ctx) => setLastAction(`Highlighted row ${(ctx.row ?? 0) + 1}`),
});
cm.registerItem({
id: 'column-info',
label: 'ℹ️ Column Info',
contexts: ['header'],
action: (ctx) => {
const col = employeeColumns[ctx.col ?? 0];
setLastAction(`Column: ${col?.title ?? 'unknown'}, type: ${col?.type ?? 'string'}`);
},
});
cm.registerItem({
id: 'select-all',
label: '☐ Select All',
shortcut: 'Ctrl+A',
contexts: ['corner'],
action: () => setLastAction('Select all triggered from corner'),
});
return () => {
cm.unregisterItem('highlight-row');
cm.unregisterItem('column-info');
cm.unregisterItem('select-all');
};
}, []);
return (
<DemoWrapper title="Live Demo" description="Right-click cells, headers, row numbers, or the corner to see context-specific menus with custom items." height={440}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={{ padding: '0.5rem 0.75rem', fontSize: '0.8rem', color: '#64748b', borderBottom: '1px solid #e2e8f0', flexShrink: 0 }}>
{lastAction}
</div>
<div style={{ flex: 1 }}>
<WitTable
theme={witTheme}
ref={tableRef}
columns={employeeColumns}
data={data}
showRowNumbers
style={{ width: '100%', height: '100%' }}
/>
</div>
</div>
</DemoWrapper>
);
}

Add custom items to the context menu through the engine API:

import { useRef, useEffect } from 'react';
import { WitTable, WitTableRef } from '@witqq/spreadsheet-react';
function App() {
const ref = useRef<WitTableRef>(null);
useEffect(() => {
const cm = ref.current?.getInstance().getContextMenuManager();
cm?.registerItem({
id: 'insert-row-above',
label: 'Insert Row Above',
shortcut: 'Ctrl+Shift+I',
contexts: ['cell', 'row-number'],
action: (ctx) => {
console.log('Insert row above', ctx.row);
},
});
cm?.registerItem({
id: 'format-column',
label: 'Format Column',
icon: '🎨',
contexts: ['header'],
action: (ctx) => {
console.log('Format column', ctx.col);
},
isDisabled: (ctx) => ctx.col === 0,
});
return () => {
cm?.unregisterItem('insert-row-above');
cm?.unregisterItem('format-column');
};
}, []);
return <WitTable ref={ref} columns={columns} data={data} />;
}

Items appear based on their contexts array:

ContextTrigger AreaTypical Actions
cellAny data cellCopy, paste, insert, delete
headerColumn headerSort, filter, resize, format
row-numberRow number gutterInsert row, delete row
cornerTop-left corner cellSelect all, clear all
MethodSignatureDescription
registerItem(item: ContextMenuItem) => voidAdd menu item
unregisterItem(id: string) => voidRemove menu item
close() => voidProgrammatically close menu
interface ContextMenuItem {
id: string;
label: string;
icon?: string;
shortcut?: string;
contexts: Array<'cell' | 'header' | 'row-number' | 'corner'>;
action: (ctx: MenuContext) => void;
isDisabled?: (ctx: MenuContext) => boolean;
isVisible?: (ctx: MenuContext) => boolean;
}

When using the context menu as a plugin, use createContextMenuPlugin and its helper functions:

import { createContextMenuPlugin, createDefaultMenuItems } from '@witqq/spreadsheet-plugins';
const plugin = createContextMenuPlugin();
engine.installPlugin(plugin);
// Get the default items (copy, paste, insert row, delete row, etc.)
const defaultItems = createDefaultMenuItems();

Register or unregister items via the plugin API:

import { registerMenuItem, unregisterMenuItem } from '@witqq/spreadsheet-plugins';
registerMenuItem(pluginApi, {
id: 'custom-action',
label: 'Custom Action',
contexts: ['cell'],
action: (ctx) => console.log('Custom action on', ctx.row, ctx.col),
});
unregisterMenuItem(pluginApi, 'custom-action');