demo D79
A detail pane beside the list in htmx
The detail renders into your own element, one record at a time
detail: { target: '#pane' }
This grid renders the detail for the selected row into an element you own beside the list, one record at a time. It suits a record with more fields than fit in an expanding row, keeping the list compact while the full detail shows alongside.
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">
<div style="display: flex; gap: 1rem; align-items: stretch">
<!-- A real, server-rendered table: readable with JavaScript off. data-field on
each header names the column that cell becomes, and data-id on each row is
the key the grid carries it under. -->
<table id="detail-pane-table" data-lattice-grid>
<thead>
<tr>
<th data-field="circuit">Circuit</th>
<th data-field="customer">Customer</th>
<th data-field="portCount">Ports</th>
<th data-field="charge">Monthly charge</th>
</tr>
</thead>
<tbody>
<tr data-id="C0"><td>ETH-00000</td><td>Verity Bank</td><td>6</td><td>1197</td></tr>
<tr data-id="C1"><td>ETH-00001</td><td>Northgate Care</td><td>2</td><td>509</td></tr>
<tr data-id="C2"><td>ETH-00002</td><td>Tamar Energy</td><td>2</td><td>3444</td></tr>
<!-- the rest of the rows, rendered by your server -->
</tbody>
</table>
<!-- With a target, detail.target renders into this element only once a row
is selected, so it carries its own placeholder until then. -->
<div id="detail" style="flex: 1 1 22rem; min-width: 20rem; height: 540px">Select a circuit to see its ports here.</div>
</div>
<script type="module">
import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';
// The markup carries the rows; a server-rendered table's column types are
// declared in the config hydrateTable takes. The grid replaces the table
// where it stood.
const grid = hydrateTable(document.getElementById('detail-pane-table'), {
selection: 'single',
toolPanel: {
side: 'left',
panels: ['columns', 'filters', 'views', 'quick'],
actions: ['undo', 'redo', 'export', 'restore', 'maximise'],
exportName: 'lattice-demo',
},
detail: {
// A target keeps the list still: the row count never changes and one
// master is open at a time, so selecting a row draws its ports into the
// pane beside the list and selecting another replaces them.
target: document.getElementById('detail'),
rows: (row) => row.data.ports,
config: {
rowKey: 'port',
autoHeight: true,
columns: [
{ field: 'port', title: 'Port', layout: { width: 140 } },
{ field: 'vlan', title: 'VLAN', type: 'number', layout: { width: 100 } },
{ field: 'speed', title: 'Speed', type: 'number', format: { suffix: ' Gb' }, layout: { width: 110 } },
{
field: 'state', title: 'State', layout: { width: 130 },
cell: { decoration: 'dot', variant: { map: { up: 'success', down: 'danger', 'admin down': 'neutral' } } },
},
{ field: 'peer', title: 'Peer AS' },
],
},
},
columns: [
{ field: 'circuit', title: 'Circuit', layout: { width: 160 } },
{ field: 'customer', title: 'Customer', filter: { type: 'set' } },
{ field: 'portCount', title: 'Ports', type: 'number', layout: { width: 100 } },
{ field: 'charge', title: 'Monthly charge', type: 'number', format: { style: 'currency', currency: 'GBP', decimals: 0 }, layout: { width: 170 } },
],
});
</script>
Rendering a detail pane for the selected row
A detail pane shows the full record for whichever row is currently selected, drawn into an element you own rather than into a row that expands inline. A developer reaches for this over an inline detail row when the record has more fields than fit the grid’s columns, when the detail needs its own layout separate from the tabular one, or when the list and the detail are meant to sit side by side. Lattice Grid wires this up with detail: { target: '#pane' }, naming the element the current row’s data should render into; selecting a different row swaps the content of that target without touching the row list, so scroll position and any open branches in the tree stay put. Because this is a JavaScript data grid built around row-level updates rather than full re-renders, moving selection only re-renders the pane, not the grid body. The pane receives the row’s data as a plain object, so what appears inside it, from a field list to a nested form, is up to the markup supplied at the target. Selection moves with the keyboard, and the target element updates in place so assistive technology tracking that region announces the new content rather than losing focus context on every change.
How do I show a detail view for the selected row in a JavaScript data grid?
Set detail: { target: '#pane' } on the grid configuration, pointing at the element the detail should render into. Lattice Grid pushes the selected row’s data into that target whenever selection changes, leaving the markup and layout of the pane entirely up to you rather than constraining it to an inline expanding row.