how-to
How to copy and paste between a data grid and Excel
Last updated 22 September 2026
Select a rectangular block of cells and press Ctrl+C (Cmd+C on a Mac): the grid puts the block on the system clipboard as tab-separated text, the same form Excel, Numbers and Sheets all read as columns and rows rather than one lump of text. Copy a block from a real spreadsheet and press Ctrl+V over a cell in the grid and it lands in, cell by cell, through each destination column's own type and validation.
Below, twelve parts sit in a grid with a range already selected across the Qty and Price columns. Press Ctrl+C and the box underneath the grid fills with the exact tab-separated text that was copied, so you can see the round trip rather than take it on faith. Select cells elsewhere and copy again, or copy a block from an actual spreadsheet and paste it in with Ctrl+V.
The code
Range selection is on by default, and Ctrl+C follows from it with no extra wiring: no
button, no handler, nothing to opt into. Ctrl+V and Ctrl+D (fill down) need the target
columns to be editable too, since either one writes into the grid. The
clipboard:copy event carries the exact text a copy wrote,
{ text, ok }, which is what a page reads if it wants to show or log what
left the grid. The same operations are also callable directly:
grid.export.clipboard(opts) and grid.export.rangeText(opts) copy
from code, and grid.edit.pasteInto(anchor, text, extent) pastes tab-separated
text at a cell, using the same tiling rules as a keyboard paste: one value fills the whole
target, a smaller block repeats to fill it, and a block larger than the target is clipped
rather than creating rows.
Two files: index.html loads the grid and declares the mount point, demo.js configures and creates it. Copy both as they are below and it runs.
index.html
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to copy and paste between a data grid and Excel</title>
<meta
name="description"
content="Select a range of cells, copy it with Ctrl+C and see the tab-separated text Excel and Sheets read as columns and rows. Paste a block copied from Excel back in and it lands cell by cell, typed. Built with Lattice Grid loaded by script tag, no install and no build."
/>
<link rel="icon" href="data:," />
<!--
The grid's stylesheet, from the Lattice Grid CDN. The address names the exact
release, 1.119.0, and carries the hash of the file it expects, so the
page can never quietly pick up a different build than the one it was
checked against.
-->
<link
rel="stylesheet"
href="https://cdn.latticegrid.dev/lattice-grid@1.119.0/lattice-grid.min.css"
integrity="sha384-xAUBbotz0ivGuPPFI8M27asveOLuZiAMWu3202beZvFAEMlpgfuqOrFCb6veOibi"
crossorigin="anonymous"
/>
<style>
body { margin: 0; font-family: system-ui, sans-serif; background: #f4f6f9; color: #131a24; }
header { padding: 1.5rem 1.5rem 0.5rem; max-width: 960px; margin: 0 auto; }
header p { color: #4a5568; }
header a { color: #2d6bff; }
main { max-width: 960px; margin: 0 auto; padding: 0 1.5rem 2.5rem; }
#grid { height: 300px; margin: 0 0 1rem; }
#stat { font-size: 0.9rem; color: #4a5568; margin: 0 0 1.5rem; }
label { display: block; font-size: 0.9rem; font-weight: 600; margin: 0 0 0.4rem; }
#copied {
width: 100%; box-sizing: border-box; height: 110px; resize: vertical;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem;
padding: 0.6rem 0.75rem; border: 1px solid #ccd3dc; border-radius: 4px;
background: #fff; color: #131a24; white-space: pre;
}
.caveats { font-size: 0.9rem; color: #4a5568; }
</style>
</head>
<body>
<header>
<h1>How to copy and paste between a data grid and Excel</h1>
<p>
Drag across a block of cells, or extend it with Shift and the arrow keys, then press
Ctrl+C (Cmd+C on a Mac). The grid puts the block on the clipboard as tab-separated text,
the same form Excel, Numbers and Sheets all read as columns and rows, and the box below
shows exactly what was copied. Copy a block from a real spreadsheet and press Ctrl+V over
a cell here and it pastes in, cell by cell, through each column's own type. Read the
<a href="https://www.latticegrid.dev/docs/how-to/copy-and-paste-with-excel/">full how-to</a>
on latticegrid.dev.
</p>
</header>
<main>
<div id="grid"></div>
<p id="stat"></p>
<label for="copied">What was copied (tab-separated text)</label>
<textarea id="copied" readonly></textarea>
<p class="caveats">
Two things to know: the browser asks permission before a page can read or write the
system clipboard, so the first copy or paste may prompt; and a read-only cell is never a
paste target, so pasting onto one changes nothing.
</p>
</main>
<!--
The library, as a classic script tag. No npm install, no bundler, no
type="module": the file runs as it arrives and leaves the LatticeGrid
global behind.
-->
<script
src="https://cdn.latticegrid.dev/lattice-grid@1.119.0/lattice-grid.min.js"
integrity="sha384-QIxY4r3eW/HnAjlSEjU5a4mdgubDy5leOo5Z1ZooWsWf5Zfw3/KWfyh9qAklmbca"
crossorigin="anonymous"
></script>
<script src="./demo.js"></script>
</body>
</html>
demo.js
/**
* Copy a range with Ctrl+C and paste a block from Excel back in with Ctrl+V.
*
* Range selection is on by default, and it is what Ctrl+C and Ctrl+V act on:
* the grid writes the selected rectangle to the system
* clipboard as tab-separated values on copy, and on paste reads the same
* shape back in, tiling it from the active cell through each column's own
* type. The `clipboard:copy` event carries the exact text that was written,
* which is what fills the box below so a reader can see the round trip
* rather than take it on faith.
*/
// Tied to toclocoinc.github.io only; has no effect anywhere else and needs
// no key at all to run this page from a local copy.
LatticeGrid.setLicence(
'LG1.eyJ2IjoxLCJwIjoibGF0dGljZS1ncmlkIiwidCI6IlRPQ0xPQ08gSW5jIC0gcHVibGljIGRlbW9zIiwiZSI6IjIwMzAtMDEtMDEiLCJkIjpbInRvY2xvY29pbmMuZ2l0aHViLmlvIl19.9De42ua3aCGpiMB6EVRP7Tv-upUlDI-0T07rlSPzvCrsqg8t4YJi7SRnStEpAg48uzmcG7il1fR_TfwkUE7iCA'
);
const PARTS = [
{ id: 'P01', item: 'Steel bolt M8', qty: 240, price: 0.18 },
{ id: 'P02', item: 'Steel bolt M10', qty: 180, price: 0.24 },
{ id: 'P03', item: 'Hex nut M8', qty: 240, price: 0.09 },
{ id: 'P04', item: 'Washer M8', qty: 480, price: 0.04 },
{ id: 'P05', item: 'Cable tie 200mm', qty: 600, price: 0.06 },
{ id: 'P06', item: 'Cable gland M20', qty: 90, price: 0.55 },
{ id: 'P07', item: 'Terminal block 4-way', qty: 60, price: 1.85 },
{ id: 'P08', item: 'Din rail 1m', qty: 30, price: 3.40 },
{ id: 'P09', item: 'Enclosure 200x150', qty: 12, price: 14.20 },
{ id: 'P10', item: 'Enclosure 300x200', qty: 8, price: 21.60 },
{ id: 'P11', item: 'Fuse 5A', qty: 150, price: 0.32 },
{ id: 'P12', item: 'Fuse holder', qty: 75, price: 0.95 },
];
const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
rowKey: 'id',
selection: { mode: 'multiple', ranges: true },
edit: true,
columns: [
{ field: 'item', title: 'Item', layout: { flex: 1, min: 180 } },
{ field: 'qty', title: 'Qty', type: 'number', edit: true, layout: { width: 90 } },
{
field: 'price', title: 'Unit price', type: 'number', edit: true,
format: { style: 'currency', currency: 'USD', decimals: 2 },
layout: { width: 130 },
},
],
rows: PARTS,
});
window.__demoGrid = grid; // read by tools/verify.mjs
// A range already selected on load, so there is something to copy straight away.
grid.selection.setRange({ startRow: 0, endRow: 3, columns: ['qty', 'price'] });
const copied = document.getElementById('copied');
grid.on('clipboard:copy', (e) => {
copied.value = e.ok ? e.text : 'the browser refused clipboard access';
});
document.getElementById('stat').textContent =
'Drag across cells, or extend the selection with Shift and the arrow keys. ' +
'Ctrl/Cmd+C copies the range; Ctrl/Cmd+V pastes a block copied from Excel or ' +
'Sheets back into the grid, starting at the active cell.';
Try the standalone page or read the full source on GitHub, loaded by script tag with no build step.
Two things to know
- Reading or writing the system clipboard needs the browser's permission. A page usually
gets it the moment a person presses Ctrl+C or Ctrl+V themselves; calling the clipboard from
code with no keypress behind it can be refused, in which case
clipboard:copyreportsok: falseeven thoughtextstill carries what would have been copied. - A read-only cell is never a paste target. Anchoring a paste on one, or pasting a block that only reaches read-only columns, changes nothing, rather than forcing a write through a column that was deliberately closed to editing.
See the full clipboard demo for the same interop against a wider set of columns, or cell ranges and the fill handle for the other two gestures that share the same range selection, and the editable cells with validation how-to for the rule a pasted value is checked against before it commits.