Copy, paste and right-click
Select cells like in Excel: drag across them, Shift-click, use Shift with the arrow keys, or press Ctrl+A. Ctrl+C copies the range in a format Excel and…
Live demoCopy, paste & right-click
Excel-style cell ranges, copy to Excel, paste from Excel, and a right-click menu.
Code for this demo
import { createGrid, type GridApi } from '@freegridjs/core';
import { demoColumns, makeRows } from '../data';
import { button, separator, text, toggle, tr, type DemoContext } from '../ui';
export default function mount(ctx: DemoContext) {
const { lang } = ctx;
// Paste only writes to columns marked editable.
const editable = new Set(['name', 'q1', 'q2', 'q3', 'q4', 'joined', 'active', 'status']);
const columns = demoColumns(lang).map(function mark(c): typeof c {
if (c.children) return { ...c, children: c.children.map(mark) };
// email cannot be copied: it pastes as an empty cell
if (c.field === 'email') return { ...c, copyable: false, cellClass: 'demo-secret' };
return editable.has(c.field ?? '') ? { ...c, editable: true } : c;
});
const data = makeRows(500);
let grid: GridApi;
const create = (rangeSelection: boolean) => {
grid?.destroy();
grid = createGrid(ctx.host, {
columns,
data,
rowSelection: 'multiple',
locale: lang,
rangeSelection, // drag, Shift+click, Shift+arrows and Ctrl+A select cell ranges (Excel-style)
summary: { rows: [{ label: tr(lang, { en: 'Total', ko: '합계' }), columns: { q1: 'sum', q2: 'sum', q3: 'sum', q4: 'sum' } }] },
contextMenu: p => [
...p.defaultItems,
'separator',
{ label: tr(lang, { en: `Say hello to ${p.row?.name ?? ''}`, ko: `${p.row?.name ?? ''}에게 인사하기` }), action: () => ctx.log(`👋 ${p.row?.name}`) },
],
});
grid.on('rangeChange', e => e.range && ctx.log(`rangeChange → ${e.range.start.colId} ${e.range.start.rowIndex + 1} : ${e.range.end.colId} ${e.range.end.rowIndex + 1}`));
grid.on('cellValueChanged', e => ctx.log(`cellValueChanged → row ${e.rowIndex + 1} ${e.colId}: ${String(e.oldValue)} → ${String(e.newValue)}`));
grid.on('paste', e => ctx.log(`paste → changed ${e.changed}, skipped ${e.skipped}`));
};
create(true);
text(ctx, tr(lang, {
en: 'Drag across cells, press Ctrl+C and paste into Excel. Copy cells in Excel, click a Name or Q1–Q4 cell and press Ctrl+V. Right-click for the menu. Hold Ctrl to add more ranges. The Email column cannot be copied.',
ko: '셀을 드래그하고 Ctrl+C를 누른 뒤 엑셀에 붙여넣어 보세요. 엑셀에서 복사한 셀은 이름이나 1~4분기 셀을 클릭하고 Ctrl+V로 붙여넣습니다. 우클릭하면 메뉴가 열립니다. Ctrl을 누른 채 드래그하면 범위를 더 추가합니다. 이메일 컬럼은 복사되지 않습니다.',
}));
separator(ctx);
toggle(ctx, tr(lang, { en: 'Drag selects cell ranges', ko: '드래그로 셀 범위 선택' }), true, on => {
create(on);
ctx.log(`rangeSelection: ${on}`);
});
button(ctx, tr(lang, { en: 'Copy with headers', ko: '헤더 포함 복사' }), () => {
const tsv = grid.copySelection({ headers: true });
ctx.log(tr(lang, { en: `copied ${tsv.split('\r\n').length} lines`, ko: `${tsv.split('\r\n').length}줄 복사됨` }));
});
button(ctx, tr(lang, { en: 'Paste sample into Name, Q1', ko: '이름·1분기에 예시 붙여넣기' }), () => {
grid.setRange({ start: { rowIndex: 0, colId: 'name' }, end: { rowIndex: 0, colId: 'name' } });
grid.pasteText('Kim Minsu\t1,234,000\r\nLee Jiwoo\t(50)');
});
}Select cells like in Excel: drag across them, Shift-click, use Shift with the arrow keys, or press Ctrl+A. Ctrl+C copies the range in a format Excel and Google Sheets understand: numbers stay numbers, dates become YYYY-MM-DD. Ctrl+V pastes cells copied from Excel starting at the selected cell. A single copied value fills the whole selected range.
Pasting only changes columns with editable: true, so ids and computed columns stay safe. Values are converted to the column type: 1,234, ₩12,000, (5) and 12% become numbers; 2026. 3. 5. and 2026년 3월 5일 become dates. Values that can't be converted are skipped and counted in the paste event.
Hold Ctrl (⌘) while clicking or dragging to select several ranges. Ranges in the same columns or rows copy together like in Excel, and Delete and the status bar totals cover all of them. Give columns that must not be copied, such as national ids, copyable: false; their cells copy as empty text.
Right-click a cell for Copy, Copy with headers, Paste, autosize, hide and pin. Right-click a header for the column menu. Turn ranges off with rangeSelection: false, or bring back the browser menu with contextMenu: false.
const grid = createGrid(el, {
columns: [
{ field: 'id', type: 'number' }, // read-only
{ field: 'name', editable: true }, // paste allowed
{ field: 'qty', type: 'number', editable: true }, // "1,234", "(5)", "12%" are parsed
{ field: 'due', type: 'date', editable: true }, // "2026-03-05", "2026. 3. 5." …
],
data,
rangeSelection: true, // default: drag / Shift+click / Shift+arrows / Ctrl+A
contextMenu: p => [
...p.defaultItems, // Copy, Paste, Pin, Hide, …
'separator',
{ label: 'Open order', action: () => open(p.row) },
],
});
grid.on('cellValueChanged', e => save(e.row)); // persist edits
grid.on('paste', e => console.log(e.changed, 'cells changed'));
grid.setRange({ start: { rowIndex: 0, colId: 'name' }, end: { rowIndex: 9, colId: 'qty' } });
grid.copySelection({ headers: true }); // TSV, also written to the clipboard
grid.pasteText('Kim 1,234
Lee 56');The menu's Paste item reads the clipboard, which some browsers block or ask about. Ctrl+V always works because it uses the browser's own paste event.