demo D43
Skeleton loading in htmx
What a remote source looks like while a block is in flight
render: 'skeleton'
This grid shows the placeholder a column falls back to while its real value is still arriving from a remote source: grey blocks standing in for text, a pill or a sparkline. It keeps a loading screen calm and stable, so rows do not jump as data lands.
This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.4/dist/htmx.min.js"></script>
<!-- htmx asks your endpoint for the first block on load and for the next one
as the reader nears the end of what has arrived. The reply is ordinary
table markup, the same rows a server template already renders. -->
<div id="grid" hx-get="/api/rows" hx-trigger="load, lattice:scroll-near-end"
style="height: 540px"></div>
<script type="module">
import { createGrid, driveInfiniteScroll } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';
const el = document.getElementById('grid');
const grid = createGrid(el, {
// Each row's data-id arrives as __row, so that is the key here.
rowKey: '__row',
selection: 'multiple',
columns: [
{ field: 'service', title: 'Service', layout: { width: 180 } },
{ field: 'team', title: 'Team', layout: { width: 150 } },
{ field: 'owner', title: 'Owner', layout: { width: 180 } },
{ field: 'region', title: 'Region', layout: { width: 140 } },
{ field: 'status', title: 'Status', layout: { width: 130 }, cell: { render: 'pill' } },
{ field: 'utilisation', title: 'Utilisation', type: 'number', layout: { width: 140 }, format: { suffix: '%' } },
{ field: 'cost', title: 'Monthly cost', type: 'number', layout: { width: 160 }, format: { style: 'currency', currency: 'USD' } },
],
});
// Each request carries the sort, the filters and how many rows are already
// held, and the reply is added to what is there rather than swapped into the
// page, so only what the reader has reached is ever asked for.
driveInfiniteScroll(grid, el);
</script>
Skeleton screens for a grid waiting on a remote source
Skeleton loading is the renderer a column falls back to while the value behind it has not arrived yet: grey blocks standing in for text, a pill or a sparkline until the row’s real data resolves. A developer reaches for it wherever a JavaScript data grid is bound to a remote source with uneven latency (a paginated API, a search-as-you-type endpoint, a dashboard where some columns compute server-side after the first paint) and a blank or flashing cell would read as broken rather than pending. Lattice Grid sets this per column with render: 'skeleton', so a slow column can show its placeholder shape while sibling columns that already have data render normally in the same row, rather than blocking the whole grid on the last field to arrive. The placeholder draws inside the same recycled cell used for the eventual value, so swapping from skeleton to real content on data arrival is a cell repaint, not a row insert, and does not shift scroll position or trigger a relayout of neighbouring rows. For accessibility, the placeholder is exposed to assistive technology as a busy state rather than as empty or misleading text, so a screen reader does not announce a blank cell as a finished value.
How do you show a loading state in a JavaScript data grid?
Set the column’s cell.render option to 'skeleton' for any field whose value has not resolved yet. Lattice Grid draws a placeholder block in that cell until the row data updates, at which point the cell repaints with its normal renderer. This keeps pending and resolved columns visually distinct within the same row, rather than delaying the whole grid until every field is available.