Lattice Grid Buy a licence

developer guide

Columns and cell rendering: JavaScript Data Grid Guide

Defining columns, the built-in types and formatting, the cell renderers, rich cell tooltips, charts, group rows you draw yourself, and the formatting rules that colour a cell by its value.

Developer guide › Columns and cell rendering

Defining columns

A column is an object with a field (the path to read from your data) and whatever else it needs. Everything except field or id is optional.

Types and formatting

A type is not a label. It is a bundle of behaviour: how a value is formatted, parsed back from text, compared when sorting, stored in the columnar backing, written to Excel, and put on the clipboard. Setting type configures all of that at once.

Cells and renderers

By default a cell writes text. When you want more, cell takes a decoration, a named renderer, a template or a component.

Rich cell tooltips

A cell.tooltip string becomes the browser's own title. That is one line of plain text, shown on the browser's schedule, styled by the browser, and unreachable with a keyboard - it cannot show a related record, a small chart, a list of validation errors or an edit history. The object form of cell.tooltip declares a tooltip the grid draws itself instead: { render, mount, unmount }. The plain-text form is unchanged and still produces a title.

Image columns

A column whose value is a URL, drawn as a picture. Avatars beside a name, product thumbnails, company logos.

Barcode options

The barcode cell renderer, the form barcode control and the standalone renderBarcode(el, options) export all read the same BarcodeOptions: type names the symbology, value is the payload, width/height set the rendered size in CSS pixels, and moduleWidth sets the width of one module directly, overriding width - useful when a symbol must scan at a fixed module size regardless of how wide its payload makes it. quietZone is the light margin, in modules, either side of the bars; the default of 10 is the usual minimum for a reliable scan, and it is clamped to 40 so a stray large value cannot push the symbol out of view. text is the human-readable line (BarcodeTextOptions): show, position ('top' or 'bottom'), align, margin and font. foreground/background default to the theme's foreground token so the symbol follows light and dark mode like everything else on the page; print: true fixes black bars on white instead, for a label meant to be printed and scanned rather than looked at on screen. checkDigit appends (Code 39) or requires (Code 39 Extended) a modulo-43 check character; EAN-13, EAN-8, UPC-A and UPC-E always compute or verify their own, because a reader needs it to recover the data and a wrong one refuses the value rather than drawing a code that would scan incorrectly. size and encodation are read only for the 2D Data Matrix symbology: size forces one of the supported square symbol sizes rather than auto-choosing the smallest that fits, and encodation ('ascii', the default, or 'base256') forces the encodation scheme instead of the usual ASCII-first choice.

Two-line cells

A bold primary line over a quieter secondary one, taken from a second property of the same row. A name over an email, a title over a category, a company over its sector.

Cards, lists and feeds

rowTemplate draws each row with a layout of your own instead of dividing it into columns. A card list, a feed, a search-result list, a message list: any presentation where a record is a small piece of layout rather than a line of cells.

Full-width rows

A row drawn as a single band across every column instead of being divided into them: a section banner, an explanatory note, an empty-group message, a “load more” affordance, anything that belongs between rows and is not itself divided by the columns.

Group rows you draw yourself

The grid's own group row is an expander, a label and a count. When the heading has to carry more than that - a sprint section with a chevron, the section name, the points summed across it, a done/total count and a progress bar - groupRenderer hands you the whole row. It is drawn as one band across every column, over the pinned regions, and no ordinary cells are mounted underneath it.

In-cell charts

Eleven chart types for a cell: line, area, column, winloss, pie, donut, bullet, stacked, range, gauge and delta. Most read an array; bullet and gauge read a number.

Chart responsive rules

responsive on a chart is 'auto' or an array of rules, each evaluated against the chart's own container. See responsive rules for the executed example.

NameWhat it does
maxWidthMatches while the container is at most this many pixels wide (inclusive; an active rule holds 4 px further).
minWidthMatches while the container is at least this many pixels wide.
maxHeightMatches while the container is at most this many pixels tall.
minHeightMatches while the container is at least this many pixels tall.
setThe chart options laid over the base while every bound given holds; plain objects such as legend and axis merge key by key, anything else replaces.

deck.gl

bindDeck(grid, options) (modules/deckgl) draws a grid's rows with deck.gl and, optionally, lets deck's view filter the grid. deck.gl stays on your page: pass your Deck and a function that builds layers with your own deck.gl classes; the module imports nothing of deck's. A paged pushdown grid hands over the rows inside deck's view from the engine, and past viewportCap the engine's density cells with ctx.binned true. See the deck.gl adapter for the executed example.

NameWhat it does
bindDeckBind a deck.gl Deck to a grid. Returns the binding; a missing setProps, a layers that is not a function or a position that names no column is refused by name and binds nothing.
deckYour Deck instance (or a React DeckGL's deck). The binding calls setProps({ layers }) and reads the view from getViewports()[0].
layers(rows, ctx) => Layer[], called with the rows the grid shows. ctx carries features (GeoJSON), binned, pending and provenance. On the binding, layers() returns what deck was last given.
position{ lon, lat } or { geometry }: the column(s) rows are placed by.
viewportFiltertrue or { debounce }: after a pan or zoom settles, write deck's box onto the grid as one condition, and move deck to a box set from elsewhere.
viewportCapOn a paged pushdown grid, the most rows fetched for one view before density cells are handed over instead. Default 20,000.
updateRebuild the layers now; otherwise grid changes are coalesced to one rebuild per frame.
destroyRemove the viewport condition, the grid listeners and the onViewStateChange wrapper.

Leaflet

bindLeaflet(grid, options) (modules/leaflet) draws a grid's rows with Leaflet, lets the map's view filter the grid, and selects a row when its feature is clicked - the same contract as bindDeck. Leaflet stays on your page: pass your L.Map and a function that builds layers with your own L; the module imports nothing of Leaflet's. A paged pushdown grid hands over the rows inside the map's view from the engine, and past viewportCap the engine's density cells with ctx.binned true. See the Leaflet binding for the executed example.

NameWhat it does
bindLeafletBind a Leaflet map to a grid. Returns the binding; a map missing a method the binding calls, a layers that is not a function or a position that names no column is refused by name and binds nothing.
mapYour L.Map. The binding adds and removes its layers, listens for moveend/zoomend, reads getBounds() and moves it with fitBounds().
layers(rows, ctx) => L.Layer[], called on every change of the rows or the selection. ctx carries features (GeoJSON, id = row key), selected, binned, pending and provenance. The previous layers are removed from the map; on the binding, layers() returns what is on it.
position{ lon, lat } or { geometry }: the column(s) rows are placed by; a geometry cell may hold GeoJSON, WKT, WKB or GeoJSON text.
viewportFiltertrue or { debounce }: after every moveend/zoomend - including your own setView, fitBounds and flyTo - write the map's bounds onto the grid as one condition, and move the map to a box set from elsewhere.
viewportCapOn a paged pushdown grid, the most rows fetched for one view before density cells are handed over instead. Default 20,000.
selectSelect a row by key for a layer that carries no feature; null clears. A click on a feature of L.geoJSON(ctx.features) selects on its own. The grid needs selection: 'single' or 'multiple'.
updateRebuild the layers now; otherwise changes are coalesced to one rebuild per frame.
destroyRemove the layers, the viewport condition and the map and grid listeners.

Map view

createMapView(grid, options) (modules/mapview) puts a 3D map of the grid's rows beside the grid: a MapLibre map with deck.gl layers, linked both ways. The grid's filters, quick search and grouping redraw it, a grid selection flies it to the rows, a click on a feature selects the row and scrolls the grid to it, and the tooltip shows the grid's own cell text. deck.gl and MapLibre stay on your page. See the map view for the executed example.

NameWhat it does
createMapViewBuild the map view of a grid. Returns the map view; a place naming no column, a mode with nowhere to go, or missing libraries is refused by name.
geometry{ lat, lng } (or lon), { geojson } or { path }: where each row is placed. Rows with no usable place are skipped and counted.
containerThe element, or a selector for it, that the map fills.
mode'container', 'split' (beside the grid, with a draggable divider), 'tab' (the grid and the map as tabs) or 'popout' (a separate window, or a floating panel when the popup is blocked).
layer'point', 'column', 'polygon' or 'path', for the rows of its geometry; each geometry has a default.
elevationA numeric column, or one number, for the height of columns and extruded areas, in metres.
elevationScaleA multiplier on elevation. Default 1.
colorOne colour; { field, map }, a colour per value; or { field, scale: 'ramp' }, a number along the charts ramp. Default the theme's accent.
radiusA marker's radius in metres: a column or a number.
widthA route's width in pixels: a column or a number.
flowtrue or { speed, trail }: animate a flow along the routes; still for reduced motion, stopped while the page is hidden.
buildingsAdd 3D buildings from the basemap's OpenMapTiles building layer. Off by default.
basemapA MapLibre style URL or object. Default OpenFreeMap's keyless positron (dark for a dark theme).
view{ center, zoom, pitch, bearing }: the first camera, instead of fitting the drawn rows.
theme'light' or 'dark'; by default the grid's own.
tooltipThe columns the hover tooltip shows, as the grid's formatted cell text; false for none.
libs{ deck, maplibregl }; default the page's globals.
viewportCapOn a paged pushdown grid, the most rows fetched for the map's view. Default 20,000.
updateChange options and redraw now.
flyToFly to a row, or fit the map to several.
setLayerSwitch the layer kind live.
provenance{ drawn, skipped, matched, layers }: what the map drew last.
layersThe deck.gl layers last handed to the map.
on / offListen for feature:click, feature:hover, draw, load and close, and stop.
destroyRemove the map, its layers and every listener, and put the grid back where it was.

Dashboards

createDashboard(el, spec, factories) (modules/dashboard) builds a dashboard from a JSON spec - a layout, grid, chart, KPI, map and html panels, a source per panel and links between them - and hands the spec back from spec() with every window's live placement. The viewers are handed in (or found on their script-tag globals), a panel's options go to its viewer untouched, and every refusal is named. See Dashboard composition for the spec reference and the three executed recipes: one spec to a page, mixed sources and links, and an AI proposal with views and presentation.

NameWhat it does
createDashboardBuild a spec into an element. Everything the spec's problems do not touch is built; each refusal is in problems() and warned once under its catalogued id.
validateDashboardSpecEvery problem in a spec, by path (panels[2].kind), without building it.
specThe dashboard as built: panels, sources by id, the links that were made, and each window where it now is. Rebuilding from it gives the same dashboard.
applyTear down and build another spec in place: how a proposal is accepted or a stored spec restored.
applyConfigApply a keyed config diff - { add, update, remove } of panels - the way a grid applies a keyed row diff to grid.rows.apply: add new widgets, re-render only the updated ones in place, remove the gone ones, so an untouched widget keeps its viewer. This is how the Designer's config routing (designer.configRouter) drives a dashboard from a Data Router.
panels / panelThe built panel ids; one panel's element, grid and viewer.
problemsWhat the last build refused or reported: { id, path, message }.
proposeAsk the AI callback for a spec over one source's columns; resolves to { spec, warnings } and builds nothing.
saveView / views / applyViewThe spec and every grid panel's state as one named view, the saved views, and restoring one.
present / step / stopPresentingStep through the panels one at a time with the layout's maximise.
destroyRemove the links, every viewer, grid and router attachment the dashboard made, and the layout.

The Designer

createDesigner(el, options) (modules/designer) is a dashboard designer whose state is the dashboard spec extended with pages, and whose view mode is createDashboard itself. The state is plain JSON, { schemaVersion, pages, selectedPageId, panels }, and every key it does not know is carried, so setState(getState()) is the identity. See The Designer for the state reference and an executed example.

NameWhat it does
createDesignerBuild a designer into an element. Options: mode ('edit' or 'view'), state, sources (the host's live data, by id), factories (as createDashboard takes them), messages, historyDepth (how many undo steps to keep, 100 by default), keepPagesAlive (keep every visited page built across selection changes; by default a page is disposed when left), theme, and relationships and llm (kept on designer.context for the designer features that read them), and guardrails, which is live on designer.guardrails.
getState / setStateA copy of the state; replace it. An equal state changes nothing and sends no event.
migrateMigrate a state to this version and report what changed - { ok, reason, from, to, applied, changes, state } - with { dryRun: true } reporting without applying. A plain spec is wrapped into a one-page state; a newer schemaVersion is refused by name.
mode / setModeThe mode now in force; switch it. View mode is createDashboard and nothing else. Edit mode makes the windows movable and resizable and writes each placement into the page's spec.layout.windows. The change is announced.
pages / selectPageThe pages' ids and titles; build one page on the canvas. An unknown id is refused and returns false.
addPage / renamePage / movePage / removePageaddPage({ title?, duplicate?, id? }) adds a blank page or a copy of another and selects it, returning the new id (or null); renamePage(id, title), movePage(id, delta) (signed positions, clamped) and removePage(id) rename, reorder and delete, returning false when there is no such page or the change is a no-op. Each is one undoable state change and is refused by name when guardrails.pages disallows the operation. Removing the selected page selects its neighbour.
on / onStateUpdatedon('state', fn) is sent once for each change, with { state, cause }; onStateUpdated(fn) is the same. on('mode', fn) is sent on a mode change, and on('page', fn) is sent with { id, page, previousId, type } when the selected page changes.
problemsEvery page's spec problems, { page, path, message }, from the dashboard's own validator. Never throws.
getProperty / setPropertymode, state, selectedPageId and guardrails by name; setProperty('mode', 'view') switches live. Another name is refused and returns undefined or false.
undo / redo / canUndo / canRedoUndo the most recent committed change, restoring the exact previous state and firing state with cause undo; redo() re-applies it (cause redo); canUndo()/canRedo() say whether either is possible. A drag or a slider scrub coalesces into one step, the depth is historyDepth (default 100), and setState() clears the history unless it passes { keepHistory: true }. In edit mode the chrome shows Undo and Redo buttons, and Ctrl/Cmd + Z, Ctrl/Cmd + Shift + Z and Ctrl/Cmd + Y drive them, scoped to the designer so a grid widget's own Ctrl+Z is never stolen.
guardrails / setGuardrailsThe live guardrails handle: the normalised value plus the allows* predicates, refuse and restrictPanel. A restricted choice is hidden - not disabled - and a change that would add one is refused by the guardrail's name. A state that already contains a restricted panel still renders it: locked with a badge in edit mode, exploration-only in view mode. setGuardrails(value) (or setProperty('guardrails', value)) replaces them at run time and rebuilds the canvas; a non-object is refused by name.
setSources / sourcesGive a running designer new data without rebuilding it. setSources(sources) replaces the whole map in place: an id that is gone is dropped, every other is written through, and every widget, derived grid and calculated field reading a changed source re-derives - a pushdown source re-queries. sources.update(id, { rows } | { apply: { add, update, remove } }) patches one kind: 'rows' source's rows by its rowKey, exactly as grid.rows.apply patches a grid. Neither touches the selected page or widget, the undo history, or the open panels; a field a panel named that the source no longer carries is reported in problems() by name, never thrown.
chrome / commandsThe extension points: a region above the canvas, shown in edit mode, and a command registry whose commands change the state only through commit(state, cause).
theme / setThemeThe theme name on the designer root (null when it follows the page), and a live switch: 'light', 'dark', 'auto' (follows the system) or 'high-contrast'. The same as the theme option. The chrome uses only theme tokens, so the four presets restyle it. See Theme and accessibility.
announce / auditannounce(text) says a message in the polite live region, the same text twice included. audit() checks the chrome for unnamed controls, an unnamed dialog, a stray tab, extra tab stops, duplicate ids and a missing live region; an empty list passes.
shortcuts / regionsThe documented keyboard map (register({ id, keys, description, run }), list()) and the focus regions F6 cycles (register(name, el, { order }), list(), focus(name)).
selectWidget / selectedWidgetOn the canvas, a widget is selected by click, or by tabbing to it and pressing Enter; selectWidget(id) does the same programmatically (null clears it), and selectedWidget() is the selection the properties panel binds to, { id, panel }. The selected widget is ringed, focused and given the accessible name 'Chart: Revenue by region, selected', and a select event fires.
removeWidget / addWidgetRemove a widget (its panel and its window) - the Delete key and the window's close control do the same - or add one, each one undoable state change. Dragging, resizing and the keyboard move/resize write the exact cell placement back into the page spec, one state event per gesture, and rebuild no other panel.
propertiesThe properties panel for the selected widget, generated from capability metadata: source, field-to-role mapping with aggregation, a chart type switch that keeps compatible roles and reports the dropped ones, and formatting. A chart type's editor comes from the charts registry's chartEditorModel(type); the other widget kinds' from the registry's widgetEditorModel(kind). Each control is one commit (one state event, undoable); an invalid value is refused inline by the viewer's own message.
pushdownDesigning over kind: 'pushdown' sources: fields(id) / describe(id) read a source's fields from its adapter's describe() or schema with the engine's types mapped, never from a sampled page; provenance(panelId) says whether a widget's figures were computed in the engine or the client (its info badge shows the same); refusals(id) / refusal(id, { aggregation | step }) name what the adapter cannot do, with the reason, and the properties panel disables it. The option pushdownDebounceMs (150 by default) holds the engine questions of a rebuilt canvas until edits go quiet, so a burst of edits asks the engine once, for the final state.
destroyTear down the canvas's dashboard and the designer's elements. Nothing is left outside el, and no listener on window or document.

Conditional formatting

A rule is a condition and the styling it produces. compileRules() turns a list into the function cell.style already accepts.

Formatting an end user can change

compileRules() above compiles at configuration time into cell.style, which you write and a user cannot reach. grid.formatting holds the same rules as runtime state instead: the cell layer asks it on every paint, so a rule added while the grid is running takes effect on the next frame.

Highlighting

One mechanism for two jobs: the flash a changed cell makes, and a marker you paint deliberately.

Column tags

Tag columns, then let a user show only the ones carrying a chosen tag. Sixty columns of monthly figures across five years become twelve by picking a year.

Aligned grids

Two or more grids that read as one table split into sections: a summary band above a detail grid, two datasets side by side under identical columns, a frozen top section that is genuinely different data rather than a pinned row.