Lattice Grid Buy a licence

the application shell

JavaScript application layout: panels and tabs

Lay an application out as a header row above a navigation cell, a main cell and a detail cell. Drag a splitter to resize, fold a cell to its header strip, switch the tabs in the main cell, and notice that what each cell holds is still there when it comes back.

Loading a live shell…

Read the shell API → Fill a cell with a form →

rows and cols

Rows stack, cols sit side by side.

A shell is a tree of cells nested as rows and cols to any depth. The cells in rows stack from top to bottom, and the cells in cols sit side by side, left to right. It is the same as in the form module, so one habit covers both. Sizes are plain CSS flexbox: a fixed width or height never grows, and the cells with a gravity share what is left in proportion.

what the shell gives you

A layout that holds your widgets still.

Nested rows and cols

Cells nest to any depth. Each is one element, the flex item of its parent and the flex container of its children, so a leaf cell is a plain block your widget fills.

Sizing you can predict

Width and height take pixels or any CSS length such as a percentage. Gravity shares the remaining space, and minimum and maximum sizes clamp a cell exactly as the CSS properties do. A window resize reflows by CSS alone.

Splitters

Mark a cell resizable and it gets a splitter on its trailing edge that resizes it and its next neighbour together, inside both cells’ limits. Drag it, or focus it and use the arrow keys, Home and End. Double-click restores the configured sizes.

Collapse to a header strip

A collapsable cell folds to its header, and the neighbours take the space. Expanding restores the exact previous size. A folded strip among cols reads its text vertically, and each fold can be vetoed by a handler.

Tabs

A cell can declare tabs for a keyboard-operable tab strip with one piece of content per tab. A tabChange event reports every switch.

Content that survives

Attach a Lattice view, an element or an HTML string and the cell keeps that same element for its life. Hide, show, collapse, expand and tab switches never re-render or destroy it, so a widget’s state and listeners persist.

Change it while it runs

Show and hide a cell, add one to any parent, or remove one with its whole subtree, all synchronously. Each change raises a cancellable event first, then an event after it lands.

A progress overlay

Overlay an indeterminate spinner on one cell without touching its size or its content, while a slow load finishes, and clear it when it is done.

Save and restore, and htmx

One JSON snapshot holds the whole arrangement, and restoring it moves your mounted content back by cell id. On an htmx page a data attribute carries the same snapshot through history. React, Vue, Svelte and Angular components re-emit it.

a layout from a config

Describe the cells, mount the content.

Drop in one script tag, then hand createShell your cells. Every cell is in the document and already sized when the call returns, so a widget mounted on the next line measures a real size, with no redraw to wait for.

The shell is a separate, opt-in module with a global of its own. See its size next to every other module in the module map.

See the full shell API →

<!-- the shell module, as a global -->
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1/modules/shell.min.js"></script>

<script>
  var shell = LatticeGridShell.createShell(document.querySelector('#app'), {
    type: 'line',                                // a thin rule between cells
    rows: [                                      // rows stack top to bottom
      { id: 'header', height: 48 },
      { id: 'body', gravity: 1, cols: [          // cols sit side by side
        { id: 'nav', width: 200, resizable: true, collapsable: true, header: 'Views' },
        { id: 'main', gravity: 1, resizable: true,
          tabs: [{ id: 'orders', label: 'Orders' }, { id: 'notes', label: 'Notes' }] },
        { id: 'detail', width: 280, collapsable: true, header: 'Detail' }
      ] }
    ]
  });

  // The cell is already in the document and sized, so mount on the next line.
  shell.getCell('header').attach(titleBar);
  shell.getCell('nav').attach(menu);
  shell.on('tabChange', function (e) { remember(e.cellId, e.tabId); });
</script>

Build the layout on localhost, free.

Development on localhost needs no licence key, so you can lay out your application and drag every splitter before you decide anything. Licence per domain when you ship.