Skip to content

Master-Detail Plugin

The Master-Detail plugin lets you create expandable detail rows that reveal additional content beneath each master row. Perfect for order/line-item UIs, employee/department views, or any scenario where you need to show related data without navigating away.

import '@toolbox-web/grid/features/master-detail';

The key configuration is detailRenderer - a function that receives the row data and returns either an HTML string or a DOM element. This gives you complete control over what appears in the expanded detail area.

import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'orderId', header: 'Order ID' },
{ field: 'customer', header: 'Customer' },
{ field: 'total', header: 'Total', type: 'currency' }
],
features: {
masterDetail: {
detailRenderer: (row) => `
<div class="order-details">
<h4>Order Items</h4>
<ul>
${row.items.map(item => `<li>${item.name} - $${item.price}</li>`).join('')}
</ul>
</div>
`,
},
},
};
Animation Expand/collapse animation
Expand on row click Toggle detail panel by clicking row
Detail height Height of detail panel (px or auto)

See MasterDetailConfig for the full list of options and their defaults.

The animation option controls how detail rows appear/disappear:

// Slide animation (default)
features: { masterDetail: { animation: 'slide', ... } }
// Fade animation
features: { masterDetail: { animation: 'fade', ... } }
// No animation
features: { masterDetail: { animation: false, ... } }

Requires: ServerSidePluginsee Server-Side Plugin

When ServerSidePlugin is loaded, master rows are lazily loaded via virtual scrolling (infinite scroll) or pagination — the same way flat data, Tree, or Row Grouping work. MasterDetail does not claim root data, so master rows flow through ServerSide’s block cache unchanged.

Detail data can be fetched on demand or embedded in the master row:

  • Async detail data: On detail expand, MasterDetailPlugin queries datasource:fetch-children. ServerSide calls getChildRows() and delivers the result.
  • Embedded detail data: If detail data is already in the master row object (e.g. row.items), detailRenderer accesses it directly — no getChildRows() needed.

When ServerSide is not present, detailRenderer works synchronously from the row object.

import '@toolbox-web/grid/features/master-detail';
import '@toolbox-web/grid/features/server-side';
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');
grid.gridConfig = {
columns: [
{ field: 'orderId', header: 'Order ID' },
{ field: 'customer', header: 'Customer' },
{ field: 'total', header: 'Total', type: 'currency' },
],
features: {
serverSide: {
pageSize: 50,
dataSource: {
async getRows(params) {
const res = await fetch(`/api/orders?start=${params.startNode}&end=${params.endNode}`);
const data = await res.json();
return { rows: data.orders, totalNodeCount: data.total };
},
async getChildRows(params) {
const { row } = params.context;
const res = await fetch(`/api/orders/${row.id}/items`);
return { rows: await res.json() };
},
},
},
masterDetail: {
detailRenderer: (row, rowIndex) => {
const plugin = grid.getPluginByName('masterDetail');
if (plugin.isDetailLoading(rowIndex)) {
return '<div class="loading">Loading order items…</div>';
}
const items = plugin.getDetailData(rowIndex);
if (!items) {
return '<div class="loading">Loading…</div>';
}
return `
<div class="order-details">
<h4>Order Items</h4>
<ul>
${items.map((item: any) => `<li>${item.name} — $${item.price}</li>`).join('')}
</ul>
</div>
`;
},
},
},
};

Data flow:

  1. ServerSide fetches master rows in blocks (virtual scroll or pagination) → renders normally (MasterDetail does not claim root data)
  2. User scrolls → ServerSide loads more master rows on demand
  3. On detail expand → MasterDetailPlugin queries datasource:fetch-children with { source: 'master-detail', row, rowIndex }
  4. ServerSide calls getChildRows() → broadcasts datasource:children
  5. MasterDetailPlugin stores the data and re-renders the detail panel
  6. detailRenderer uses getDetailData(rowIndex) to access the async data

Async API:

const plugin = grid.getPluginByName('masterDetail');
plugin.getDetailData(rowIndex); // Get fetched detail data (or undefined if not loaded)
plugin.isDetailLoading(rowIndex); // Check if detail data is currently loading
features: {
masterDetail: {
detailRenderer: (row) => {
const childGrid = document.createElement('tbw-grid');
childGrid.style.height = '200px';
childGrid.gridConfig = { columns: [...] };
childGrid.rows = row.items || [];
return childGrid;
},
},
},

See MasterDetailPlugin for the full list of methods.

const plugin = grid.getPluginByName('masterDetail');
plugin.expand(rowIndex);
plugin.collapse(rowIndex);
plugin.toggle(rowIndex);
plugin.expandAll();
plugin.collapseAll();
plugin.isExpanded(rowIndex); // boolean

The master-detail plugin supports CSS custom properties for theming. Override these on tbw-grid or a parent container:

PropertyDefaultDescription
--tbw-master-detail-bgvar(--tbw-color-row-alt)Detail row background
--tbw-master-detail-bordervar(--tbw-color-border)Detail row border
--tbw-detail-padding1emDetail content padding
--tbw-detail-max-height31.25rem (~500px)Max height for animation
--tbw-animation-duration200msExpand/collapse animation
--tbw-animation-easingease-outAnimation curve
tbw-grid {
/* Custom master-detail styling */
--tbw-master-detail-bg: #f8f9fa;
--tbw-master-detail-border: #dee2e6;
--tbw-detail-padding: 1.5rem;
--tbw-animation-duration: 300ms;
}

The master-detail plugin uses these class names:

ClassElement
.master-detail-expanderExpander cell container
.master-detail-toggleExpand/collapse icon
.master-detail-rowDetail row container
.master-detail-row.tbw-expandingRow during expand animation
.master-detail-row.tbw-collapsingRow during collapse animation
.master-detail-cellDetail content wrapper

Click the expand arrow to open/close row details.

Event Log:
EventDetailDescription
detail-expand{ rowIndex, row, expanded }Fired when a detail row is expanded/collapsed
  • Tree — Hierarchical tree data
  • Row Grouping — Group rows by field values; master-detail toggles appear on data rows within groups
  • Selection — Row and cell selection
AI assistants: For complete API documentation, implementation guides, and code examples for this library, see https://raw.githubusercontent.com/OysteinAmundsen/toolbox/main/llms-full.txt