demo D306
A dashboard the reader can rearrange
Four different views of one dataset in windows: it opens finished and locked, one button lets a reader move, resize and close them by drag or keyboard, and the arrangement comes back out as JSON you can save
createLayout · setInteractive · getLayout
Four different views of one set of field-service jobs sit in windows: a tile rail, the job list, where the money is by region, and the dispatch board. It opens finished and locked, one button hands the arrangement over to the person using it, and whatever they end up with comes back out as plain JSON you can save against their name.
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.53.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.53.0/lattice-grid.min.js"></script>
<div id="dash" style="height: 780px"></div>
<script type="module">
import { createLayout } from '@toclocoinc/lattice-grid/modules/layout';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
import { createKPI } from '@toclocoinc/lattice-grid/modules/kpi';
import { createKanban } from '@toclocoinc/lattice-grid/modules/kanban';
// Windows on a 12 by 10 cell grid. These three say nothing about what they
// allow, so the dashboard arrives locked and looks finished. The rail says no
// to all three, which is how it stays pinned once the rest is unlocked.
const layout = createLayout(document.getElementById('dash'), {
columns: 12, rows: 10, gap: 10,
windows: [
{ id: 'health', title: 'Today at a glance', xPos: 1, yPos: 1, xSize: 12, ySize: 2,
movable: false, resizable: false, closable: false },
{ id: 'jobs', title: 'Jobs', xPos: 1, yPos: 3, xSize: 7, ySize: 4 },
{ id: 'mix', title: 'Value by region', xPos: 8, yPos: 3, xSize: 5, ySize: 4 },
{ id: 'board', title: 'Dispatch board', xPos: 1, yPos: 7, xSize: 12, ySize: 4 },
],
});
// The layout makes each window's container and never reads what you put in
// one, so any view goes in any window. Size grid columns in pixels or with
// flex here, never as a percentage: a window that can be resized changes width.
const grid = LatticeGrid.createGrid(layout.payload('jobs'), { rowKey: 'id', rows: jobs, columns });
createKPI(layout.payload('health'), { grid, tiles });
createChart({ grid, container: layout.payload('mix'), type: 'bar', x: 'region', y: 'value' });
createKanban(layout.payload('board'), { rows: jobs, rowKey: 'id', columnProperty: 'status', columns: lanes });
// One button, both ways. Nothing is destroyed and nothing rebuilt, so every
// view survives the toggle with its scroll position, selection and data.
let editing = false;
editButton.addEventListener('click', () => {
editing = !editing;
layout.setInteractive(editing);
editButton.textContent = editing ? 'Done' : 'Edit layout';
});
// The arrangement is plain JSON. Save it wherever you keep a user's settings.
// { columns, rows, windows: [{ id, xPos, yPos, xSize, ySize }] }
saveButton.addEventListener('click', () =>
localStorage.setItem('dash', JSON.stringify(layout.getLayout())));
const stored = localStorage.getItem('dash');
if (stored) layout.setLayout(JSON.parse(stored));
// A closed window hands its container back and nothing else, so tear down
// whatever you mounted inside it.
layout.on('window:closed', ({ id }) => views[id]?.destroy());
</script>
A dashboard that opens finished, and can still be made theirs
The first thing anyone sees here is a screen, not a construction kit. Today’s jobs at a glance, the job list, where the value sits by region, and the dispatch board, all reading the same jobs. Nothing wobbles, nothing carries a handle, and a stray drag moves nothing. That is how a dashboard should arrive.
Press Edit layout and the same screen becomes editable where it stands. Handles appear on the windows that are allowed to move. Drag one by its title bar and the others get out of the way and close the gap behind it. Pull a bottom right corner and the window resizes, and the view inside it fits itself to the room it now has: the table shares its columns out again, the chart redraws into its new frame. Close one you do not want and the rest pull up into the space. Press Done and it is a finished screen again.
Nothing is torn down and rebuilt on the way through. The table keeps its place in the rows, the chart keeps its picture, and the board keeps its cards. Unlocking a dashboard costs the person using it nothing.
Decide once what the whole dashboard allows
Watch the tile rail at the top when you press Edit layout. It does nothing. It stays exactly where it is while everything below it loosens up.
That is the useful half of the design. The three windows underneath say nothing at all about what they allow, and one call decides for all three at once, so “let people rearrange this” is one setting rather than an audit of every window you ever added. The rail says no, and no is final: unlocking never overrides a window you said should stay put. A capability can always be taken away, and it can never be granted where you said no, which is what makes a pinned masthead safe to rely on.
How do you save a dashboard layout a user has rearranged?
Read the arrangement back out and store it. It is plain JSON, and it is printed live under the dashboard on this page: drag a window and watch it change. All it carries is the size of the cell grid and where each window sits on it, so there is nothing to serialise, nothing internal to keep in step, and nothing that stops it going into a column in your own database next to a user id. Hand the same JSON back when they next sign in and the screen they built is the screen they get.
Press Save arrangement, move some windows about, then press Put it back. Even a window that was closed comes back. That round trip is the whole persistence story, and there is no part of it you have not just watched work.
It is fully operable from the keyboard
Everything above works from the keyboard, and not as a token gesture. Tab to a window’s handle, press Space to pick the window up, move it with the arrow keys, and press Enter to drop it. Escape leaves it where it was.
Each step is spoken, so someone working with a screen reader hears the window picked up, hears the column and row it is passing through as it moves, and hears where it landed.
Why not reach for a free layout library?
GridStack is MIT-licensed and free, and it is not the only one. They are good at moving boxes around a page, and nothing here is a claim to be better at that.
The difference is what is in the boxes. A dashboard is only worth building if the table, the chart and the board are still right after somebody drags a corner, and a general-purpose layout library has no way to know they exist. It moves the box, and what happens inside it is between you and whatever you put there. That is the part that eats the afternoon.
Here the windows and the views in them are one product, and which views follow their window is written down and tested rather than something you learn from a support ticket. The grid and the chart follow their window, as you can prove on this page by dragging a corner. The tile rail and the board need no telling, because they already fit whatever room they are given. It is also a dependency you do not add and a second sizing model you do not have to learn.
What does a dashboard cost to leave open all day?
Nothing you can measure. There is no timer, no frame loop and nothing polling in the background, and twelve windows holding twelve independent live grids, watched for eight seconds in a real browser, cannot be told apart from a page with no dashboard on it at all. Both sit under a tenth of one percent of a processor core, which is the noise floor rather than a difference.
That matters for the screens people never close. An operations dashboard is open from the first coffee to the last, and it should not be the reason a laptop fan comes on.
To download, it is a 75,502-byte bundle gzipped, and that is the figure to budget for. Only 11,202 bytes of it is the layout’s own code; the rest is a fixed floor that every Lattice module carries. It brings no dependencies of its own.
What it will not do
Two limits worth knowing before you build on it.
There are no responsive breakpoints. Windows go on the cells you give them and stay there. Deciding that four windows across a laptop should become four stacked on a phone is your call, and this page makes it: it picks one of three arrangements from the width it actually has and hands it over. Narrow your browser and watch the note under the buttons change as it does. A twelve-window dashboard on a phone is not a solved problem and nothing here pretends otherwise.
And inside a window that can be resized, size a grid’s columns in pixels or let them flex, rather than giving them a percentage. A percentage width is worked out once against the page and not worked out again, so it is wrong from the moment the window around it changes size. This page sizes every column in pixels and shares the spare room out again whenever a window moves.