how-to
How to make grid cells editable, with validation
Last updated 22 September 2026
Editing and validation are both declared on the column: edit: { enabled: true }
turns a cell into an input, and validation states the rule a new value must meet.
A value that fails its rule never reaches the row: the cell keeps its old value, is marked
invalid with an announced error, and the mark clears the moment a valid value replaces it.
Below, Quantity must stay between 0 and 500 and Owner may not be blank. Double-click a cell, or focus it with the keyboard and press Enter, to edit it. Try 9999 in Quantity: the edit is refused and the cell is marked invalid.
The code
validation takes required, min/max,
minLength/maxLength, a pattern, a oneOf list
and a crossField predicate that can read the rest of the row. Only a person's own
keystroke is gated this way: a value your own code writes, or one arriving from a live feed,
is treated as the authority and lands untouched. cell:edit:end fires after every
attempt, refused or not, so a page can notice only the edits that actually changed something
by checking writes.
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 make grid cells editable, with validation</title>
<meta
name="description"
content="Edit two columns in place in the same JavaScript data grid: a quantity with a range rule and an owner that cannot be blank. A refused edit is vetoed before it lands and marked invalid; accepted edits are logged below. Built with Lattice Grid loaded by script tag, no install and no build."
/>
<link rel="icon" href="data:," />
<!--
The grid's stylesheet, from jsDelivr. The address names the exact
release, 1.68.2, 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.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.68.2/lattice-grid.min.css"
integrity="sha384-mcpd7S8C5nz58bZDAXdYH6rzezEhfN7B4u2SlW426dSe20GnkxTu4TygyOILnCth"
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: 420px; }
#stat { font-size: 0.9rem; color: #4a5568; margin: 0 0 0.75rem; }
#log-panel { margin-top: 1.5rem; }
#log-panel h2 { font-size: 1rem; margin: 0 0 0.5rem; }
#log { list-style: none; margin: 0; padding: 0; font-size: 0.9rem; }
#log li { padding: 0.35rem 0; border-top: 1px solid #e2e6ec; }
#log:empty::before { content: 'No edits accepted yet.'; color: #8c97a6; }
</style>
</head>
<body>
<header>
<h1>How to make grid cells editable, with validation</h1>
<p>
Twelve stock items below have two editable columns: Quantity, which must stay between
0 and 500, and Owner, which cannot be blank. Double-click a cell, or focus it and press
Enter, to edit it. A value that breaks its rule is refused, the old value stays and the
cell is marked invalid; an accepted edit is logged beneath the grid. Read the
<a href="https://www.latticegrid.dev/docs/how-to/editable-cells-with-validation/">full how-to</a>
on latticegrid.dev.
</p>
</header>
<main>
<p id="stat"></p>
<div id="grid"></div>
<div id="log-panel">
<h2>Accepted edits</h2>
<ul id="log"></ul>
</div>
</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.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.68.2/lattice-grid.min.js"
integrity="sha384-vCzLyFYn0T0lz/vkdH4x0JpJZkOazZgI2LiGui7lm5uerdZd0Z46G9hr3Aq1FFPS"
crossorigin="anonymous"
></script>
<script src="./demo.js"></script>
</body>
</html>
demo.js
/**
* Make two columns editable, and give each one a rule for what a good value
* looks like.
*
* `edit: { enabled: true }` on a column turns its cells into inputs on
* double-click or Enter. `validation` on the same column states the rule as
* data: `min`/`max` for the quantity, `required` for the owner. A value that
* fails its rule is vetoed before it is written, the cell keeps its old
* value and the grid marks it invalid; nothing here wires that, it is the
* grid's own behaviour. `cell:edit:end` fires after every attempt, so
* listening for one with `writes` is how a page notices an edit actually
* landed.
*/
// 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 ITEMS = [
{ id: 'SKU-01', item: 'Steel bolts (M6)', quantity: 240, owner: 'Priya Shah' },
{ id: 'SKU-02', item: 'Steel bolts (M8)', quantity: 180, owner: 'Priya Shah' },
{ id: 'SKU-03', item: 'Rubber gaskets', quantity: 95, owner: 'Callum Reid' },
{ id: 'SKU-04', item: 'Copper wire (10m)', quantity: 60, owner: 'Callum Reid' },
{ id: 'SKU-05', item: 'Ball bearings', quantity: 310, owner: 'Aisha Bello' },
{ id: 'SKU-06', item: 'Hex nuts (M6)', quantity: 420, owner: 'Aisha Bello' },
{ id: 'SKU-07', item: 'Washers (flat)', quantity: 150, owner: 'Tom Fenwick' },
{ id: 'SKU-08', item: 'Cable ties', quantity: 275, owner: 'Tom Fenwick' },
{ id: 'SKU-09', item: 'Sealant tubes', quantity: 40, owner: 'Diego Marín' },
{ id: 'SKU-10', item: 'Grease cartridges', quantity: 85, owner: 'Diego Marín' },
{ id: 'SKU-11', item: 'Filter cartridges', quantity: 130, owner: 'Wren Ashby' },
{ id: 'SKU-12', item: 'Pressure gauges', quantity: 22, owner: 'Wren Ashby' },
];
const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
rowKey: 'id',
columns: [
{ field: 'id', title: 'SKU', layout: { width: 90 } },
{ field: 'item', title: 'Item', layout: { flex: 1, min: 170 } },
{
field: 'quantity', title: 'Quantity', type: 'number',
edit: { enabled: true },
validation: {
required: true, min: 0, max: 500,
messages: { min: 'Quantity cannot be negative.', max: '500 at most in stock.' },
},
layout: { width: 110 },
},
{
field: 'owner', title: 'Owner',
edit: { enabled: true },
validation: { required: true, messages: { required: 'An owner is required.' } },
layout: { width: 150 },
},
],
rows: ITEMS,
});
window.__demoGrid = grid; // read by tools/verify.mjs
const log = document.getElementById('log');
grid.on('cell:edit:end', (e) => {
if (!e.writes || !e.writes.length) return; // refused, cancelled, or nothing changed
const write = e.writes[0];
if (Object.is(write.before, write.after)) return; // committed but no actual change (e.g. Enter's own move to the next row)
const li = document.createElement('li');
li.textContent = `${e.key} ${write.colId}: ${write.before ?? '(blank)'} → ${write.after}`;
log.prepend(li);
});
document.getElementById('stat').textContent =
`${ITEMS.length} stock items; Quantity (0-500) and Owner (required) are editable`;
Try the standalone page or read the full source on GitHub, loaded by script tag with no build step.
Two things to know
- Declaring
validationas data keeps every rule in one readable place and lets a rule carry its own message throughmessages, so a refusal reads as "Use CHG, INC or REQ followed by four digits" rather than a generic error. Reach for acrossFieldpredicate when a rule needs to read another value on the same row, such as a budget capped by a quantity. - Pressing Enter to commit also moves editing to the same column on the row below, the way a spreadsheet does; Escape cancels instead of committing, and Tab commits and moves across to the next editable column.
See the full inline editing demo for editing without validation, or the declarative validation demo for every rule kind, including a cross-field cap that moves with the row.