Skip to content

API Reference

Complete reference for the <tbw-grid> component API.

<tbw-grid></tbw-grid>

The custom element is registered as tbw-grid (Toolbox Web Grid).

PropertyTypeDefaultDescription
rowsT[][]Array of row data objects
columnsColumnConfig[][]Column configuration array
gridConfigGridConfig<T>{}Full configuration object
PropertyTypeDefaultDescription
fitMode'stretch' | 'fixed''stretch'Column sizing strategy
PropertyTypeDefaultDescription
loadingboolean | LoadingContextfalseGrid-level loading state with optional renderer
columnStateGridColumnState[]-Get/set column widths, order, visibility, sort
PropertyTypeDescription
changedRowsT[]Rows with pending edits (requires EditingPlugin)
sortStateMap<string, SortState>Current sort state per column

The grid supports two forms of declarative HTML configuration:

  1. HTML Attributes — JSON-serialized values on the <tbw-grid> element itself
  2. Light DOM Elements — Child elements nested inside <tbw-grid> for more readable markup

Both approaches can be combined. Light DOM elements take precedence over JSON attributes for the same configuration (e.g., <tbw-grid-column> elements override the columns attribute).

Attributes on the <tbw-grid> element for simple or programmatically-generated configuration:

AttributeMaps to PropertyTypeDescription
rowsrowsJSONRow data as JSON array
columnscolumnsJSONColumn config as JSON array
grid-configgridConfigJSONFull config object as JSON
fit-modefitModestring'stretch' or 'fixed'
<tbw-grid
rows='[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]'
columns='[{"field":"id","header":"ID"},{"field":"name","header":"Name"}]'
fit-mode="stretch"
></tbw-grid>

Child elements inside <tbw-grid> for more readable, template-friendly configuration. Preferred for framework templates (Angular, Vue, etc.) and complex column setups with custom renderers/editors.

Define column configuration declaratively.

AttributeTypeDescription
fieldstringRequired. Data field key
headerstringColumn header text
typestringColumn type: 'string', 'number', 'boolean', 'date', 'select'
widthstringColumn width (e.g., "120", "100px", "20%")
min-widthnumberMinimum column width in pixels
sortablebooleanEnable sorting (presence = true)
resizablebooleanEnable resizing (presence = true)
editablebooleanEnable editing (presence = true, requires EditingPlugin)
optionsstringSelect options: "value1:Label1,value2:Label2" or "val1,val2"
rendererstringNamed renderer (for framework adapter lookup)
editorstringNamed editor (for framework adapter lookup)
<tbw-grid>
<tbw-grid-column field="id" header="ID" type="number" width="80"></tbw-grid-column>
<tbw-grid-column field="name" header="Name" sortable resizable></tbw-grid-column>
<tbw-grid-column field="role" type="select" options="admin:Admin,user:User"></tbw-grid-column>
</tbw-grid>

Custom view template inside a column. Content is used as the cell renderer.

<tbw-grid-column field="status">
<tbw-grid-column-view>
<span class="badge">{{ value }}</span>
</tbw-grid-column-view>
</tbw-grid-column>

Custom editor template inside a column (requires EditingPlugin).

<tbw-grid-column field="name" editable>
<tbw-grid-column-editor>
<input type="text" />
</tbw-grid-column-editor>
</tbw-grid-column>

Custom header template inside a column.

<tbw-grid-column field="status">
<tbw-grid-column-header>
<strong>Status</strong> <span class="required">*</span>
</tbw-grid-column-header>
</tbw-grid-column>

Configure the shell header bar.

AttributeTypeDescription
titlestringGrid title (left side)
<tbw-grid>
<tbw-grid-header title="Employee Directory">
<tbw-grid-header-content>
<span>20 employees</span>
</tbw-grid-header-content>
</tbw-grid-header>
</tbw-grid>

Custom content in the shell header center area. Must be nested inside <tbw-grid-header>.

<tbw-grid-header title="My Grid">
<tbw-grid-header-content>
<input type="search" placeholder="Search..." />
</tbw-grid-header-content>
</tbw-grid-header>

Container for toolbar buttons (right side of shell header).

<tbw-grid>
<tbw-grid-tool-buttons>
<button class="tbw-toolbar-btn" title="Export">📥</button>
<button class="tbw-toolbar-btn" title="Print">🖨️</button>
</tbw-grid-tool-buttons>
</tbw-grid>

Define a custom tool panel for the sidebar.

AttributeTypeDescription
idstringRequired. Unique panel identifier
titlestringRequired. Panel title in accordion header
iconstringIcon for accordion header (emoji or text)
tooltipstringTooltip for accordion header
ordernumberPanel order priority (lower = first, default: 100)
<tbw-grid>
<tbw-grid-tool-panel id="filters" title="Filters" icon="🔍" order="10">
<div class="filter-panel">
<label>Status: <select>...</select></label>
</div>
</tbw-grid-tool-panel>
</tbw-grid>

Define the detail panel template for master-detail rows. Requires the MasterDetailPlugin.

AttributeTypeDescription
animation'slide' | 'fade' | 'false'Panel expand/collapse animation (default: 'slide')
show-expand-columnbooleanShow expand/collapse column (default: true)
expand-on-row-clickbooleanExpand when row clicked (default: false)
collapse-on-click-outsidebooleanCollapse when clicking outside (default: false)
heightnumber | 'auto'Panel height in pixels or auto (default: 'auto')
<tbw-grid>
<tbw-grid-detail animation="slide" expand-on-row-click="true">
<div class="detail-panel">
<h3>{{ row.name }}</h3>
<p>{{ row.description }}</p>
</div>
</tbw-grid-detail>
</tbw-grid>

See the MasterDetailPlugin documentation for more details.

<tbw-grid-responsive-card> (ResponsivePlugin)
Section titled “<tbw-grid-responsive-card> (ResponsivePlugin)”

Define a custom card template for responsive mode. Requires the ResponsivePlugin.

AttributeTypeDescription
breakpointnumberWidth threshold in pixels for responsive mode
card-row-heightnumber | 'auto'Card height in pixels or auto (default: 'auto')
hidden-columnsstringComma-separated field names to hide in card mode
hide-headerbooleanHide header row in responsive mode (default: true)
debounce-msnumberResize debounce delay in ms (default: 100)
<tbw-grid>
<tbw-grid-responsive-card breakpoint="500" card-row-height="80" hidden-columns="createdAt, updatedAt">
<div class="custom-card">
<strong>{{ row.name }}</strong>
<span>{{ row.email }}</span>
</div>
</tbw-grid-responsive-card>
</tbw-grid>

See the ResponsivePlugin documentation for more details.

import { createGrid, queryGrid } from '@toolbox-web/grid';
// Create a new grid element with configuration
const grid = createGrid<Employee>({
columns: [{ field: 'name' }, { field: 'email' }],
fitMode: 'stretch',
});
document.body.appendChild(grid);
grid.rows = employees;
// Query an existing grid with type safety
const existingGrid = queryGrid<Employee>('#my-grid');
if (existingGrid) {
existingGrid.rows = newData;
}
// Update row data
grid.rows = newData;
// Wait for grid to be ready
await grid.ready();
// Force layout recalculation
await grid.forceLayout();
// Get current configuration (readonly)
const config = await grid.getConfig();

The Row Update API provides ID-based access to individual rows for reading and updating data. This is especially useful when you need to update rows after external changes (e.g., API responses, WebSocket events) without replacing the entire dataset.

The grid automatically determines row IDs using these sources (in order of precedence):

  1. getRowId function in gridConfig (custom ID resolution)
  2. id property on the row object
  3. _id property on the row object
// Configure custom row ID resolution
grid.gridConfig = {
columns: [...],
getRowId: (row) => row.employeeId, // Use a custom field as ID
};
// Get a row's ID
const id = grid.getRowId(row);
console.log(id); // "EMP-123"
// Get a single row by its ID
const employee = grid.getRow('EMP-123');
if (employee) {
console.log(employee.name);
}
// Returns undefined if the row is not found
const missing = grid.getRow('unknown-id');
console.log(missing); // undefined
// Update a single row by ID
grid.updateRow('EMP-123', { status: 'active', salary: 75000 });
// Batch update multiple rows
grid.updateRows([
{ id: 'EMP-123', changes: { status: 'active' } },
{ id: 'EMP-456', changes: { department: 'Engineering' } },
]);
// Specify the update source (default: 'api')
// Valid sources: 'user' | 'cascade' | 'api'
grid.updateRow('EMP-123', { status: 'inactive' }, 'api');
grid.updateRows(updates, 'api');

The cell-change event fires whenever row data is updated via the Row Update API:

grid.on('cell-change', ({ rowId, changes, source, row }) => {
console.log(`Row ${rowId} updated via ${source}:`);
console.log('Changes:', changes); // { status: 'active' }
console.log('Full row:', row); // Complete row object after update
});
// Get all columns with visibility info
const columns = grid.getAllColumns();
// Returns: Array<{ field, header, visible, lockVisible? }>
// Show/hide columns
grid.setColumnVisible('fieldName', false);
grid.toggleColumnVisibility('fieldName');
grid.showAllColumns();
// Check column visibility
const isVisible = grid.isColumnVisible('fieldName');
// Reorder columns
grid.setColumnOrder(['id', 'name', 'email']);
const order = grid.getColumnOrder();
// Start bulk/row editing programmatically
await grid.beginBulkEdit(rowIndex);
// Commit active row edit
await grid.commitActiveRowEdit();
// Cancel active row edit
await grid.cancelActiveRowEdit();
// Get pending changes (array of changed rows)
const changes = grid.changedRows;
// Get IDs of changed rows (requires getRowId)
const ids = grid.changedRowIds;
// Reset changed rows tracking
await grid.resetChangedRows();
MethodReturnsDescription
getPluginByName(name)Plugin | undefinedGet plugin instance by name — preferred (type-safe, no import needed)
getPlugin(PluginClass)P | undefinedGet plugin instance by class (requires import)

getPluginByName is type-safe for all built-in plugins via the PluginNameMap interface — TypeScript automatically returns the correct plugin type (e.g., SelectionPlugin for 'selection').

// Preferred — type-safe, no import needed
const selection = grid.getPluginByName('selection');
selection?.selectAll(); // ✅ SelectionPlugin methods are available
// Alternative — get by class (requires plugin import)
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
const sel = grid.getPlugin(SelectionPlugin);
// Check if plugin is registered
if (selection) {
selection.selectAll();
}

The grid uses light DOM — standard CSS targeting elements inside <tbw-grid> works normally (global stylesheets, <style> in <head>, external CSS files).

For programmatic runtime styles, use registerStyles() which injects CSS via adoptedStyleSheets:

// Inject styles programmatically at runtime
grid.registerStyles('my-id', '.my-class { color: blue; }');
// Remove when no longer needed
grid.unregisterStyles('my-id');

When you set rows, the grid automatically re-applies all active processing — sorting, filtering, grouping — so the data stays consistent. This is the right behavior for data refreshes (e.g., API responses), but not for manual row insertion or removal where you want the new row to stay exactly where you placed it.

insertRow(index, row) and removeRow(index) operate directly on the current view without running the plugin pipeline. The row is also added to / removed from the source data so that the next full pipeline run includes it correctly.

Both methods auto-animate by default ('insert' / 'remove' animation). Pass false as the last argument to skip animation. They return Promises that resolve when the animation completes.

// Insert a row at visible index 3 (auto-animates)
grid.insertRow(3, newEmployee);
// Insert without animation
grid.insertRow(3, newEmployee, false);
// Remove with fade-out animation (default) — await to ensure removal
await grid.removeRow(5);
// Remove immediately, no animation
await grid.removeRow(5, false);

All animation methods return Promises that resolve when the animation completes.

import type { RowAnimationType } from '@toolbox-web/grid';
// Animate a single row by index (await to know when done)
await grid.animateRow(rowIndex, 'change'); // 'insert' | 'change' | 'remove'
// Animate a row by its ID
await grid.animateRowById('EMP-123', 'insert');
// Animate multiple rows at once
await grid.animateRows([0, 1, 2], 'change');
// Grid-level loading
grid.loading = true;
grid.loading = { message: 'Loading data...' };
grid.loading = { renderer: (container) => { container.textContent = 'Custom...'; } };
// Row-level loading (by row ID, not index)
grid.setRowLoading('emp-123', true);
grid.setRowLoading('emp-123', false);
// Cell-level loading (by row ID and field name)
grid.setCellLoading('emp-123', 'email', true);
grid.setCellLoading('emp-123', 'email', false);
// Get current column state (for saving)
const state = grid.getColumnState();
localStorage.setItem('gridState', JSON.stringify(state));
// Restore column state
const saved = localStorage.getItem('gridState');
if (saved) {
grid.columnState = JSON.parse(saved);
}
// Reset to initial configuration
grid.resetColumnState();
// Register a tool panel
grid.registerToolPanel({
id: 'myPanel',
title: 'My Panel',
icon: '⚙️',
render: (container) => {
container.innerHTML = '<div>Panel content</div>';
},
});
// Open/close/toggle the tool panel sidebar
grid.openToolPanel();
grid.closeToolPanel();
grid.toggleToolPanel();
// Toggle specific accordion sections
grid.toggleToolPanelSection('myPanel');
// Check panel state
const isOpen = grid.isToolPanelOpen;
const sections = grid.expandedToolPanelSections;
// Get registered panels
const panels = grid.getToolPanels();

Register custom content in the grid’s header bar (above column headers) or toolbar area:

// Register header content (e.g., a search box)
grid.registerHeaderContent({
id: 'global-search',
order: 10,
render: (container) => {
const input = document.createElement('input');
input.type = 'search';
input.placeholder = 'Search all columns...';
container.appendChild(input);
},
});
// Unregister header content
grid.unregisterHeaderContent('global-search');
// Get all registered header content definitions
const headerContents = grid.getHeaderContents();
// Register toolbar content (e.g., action buttons)
grid.registerToolbarContent({
id: 'export-buttons',
order: 100,
render: (container) => {
const btn = document.createElement('button');
btn.textContent = 'Export CSV';
btn.className = 'tbw-toolbar-btn';
container.appendChild(btn);
},
});
// Unregister toolbar content
grid.unregisterToolbarContent('export-buttons');
// Get all registered toolbar content definitions
const toolbarContents = grid.getToolbarContents();
// Inject styles programmatically at runtime (uses adoptedStyleSheets)
grid.registerStyles('my-styles', '.my-class { color: blue; }');
// Remove when no longer needed
grid.unregisterStyles('my-styles');
// Get list of registered custom style IDs
const styleIds = grid.getRegisteredStyles();
// Get the default row height in pixels
// For fixed heights: the configured or CSS-measured row height
// For variable heights: the average/estimated row height
const height = grid.defaultRowHeight;

When building custom editors that render outside the grid DOM (overlays, datepickers, dropdowns appended to <body>), use the focus container registry so the grid treats focus inside those elements as “still in the grid”.

// Register an overlay panel so focus moving into it doesn't close the editor
const panel = document.createElement('div');
document.body.appendChild(panel);
grid.registerExternalFocusContainer(panel);
// When the overlay is torn down, unregister it
grid.unregisterExternalFocusContainer(panel);
// Check whether focus is logically inside the grid (own DOM + external containers)
const hasFocus = grid.containsFocus(); // checks document.activeElement
const nodeInGrid = grid.containsFocus(someNode); // checks specific node

Programmatic cell focus and scroll-to-row:

// Focus a cell by column index or field name
grid.focusCell(0, 2); // row 0, column index 2
grid.focusCell(5, 'email'); // row 5, field 'email'
// Read the currently focused cell
const cell = grid.focusedCell;
if (cell) {
console.log(`Row ${cell.rowIndex}, field "${cell.field}"`);
}
// Scroll a row into view (no-op if already visible)
grid.scrollToRow(42);
// Center the row with smooth scrolling
grid.scrollToRow(42, { align: 'center', behavior: 'smooth' });
// Scroll by row ID (requires getRowId or id/_id property)
grid.scrollToRowById('emp-42', { align: 'center' });

ScrollToRowOptions:

OptionTypeDefaultDescription
align'start' | 'center' | 'end' | 'nearest''nearest'Where to position the row
behavior'instant' | 'smooth''instant'Scroll animation

The grid dispatches standard CustomEvents that bubble up the DOM. All events use bubbles: true and composed: true.

Click cells, sort columns, resize columns to see events.
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid<Employee>('#my-grid');
// Type-safe event listening
grid.on('cell-click', ({ row, field }) => {
console.log(row, field);
});
// Cancelable events — call preventDefault() to reject the action
grid.on('cell-commit', (detail, e) => {
if (!isValid(detail.value)) {
e.preventDefault(); // Rejects the edit
}
});

Four events support preventDefault() to reject the action:

EventPluginWhat Canceling Does
cell-commitEditingReverts the cell to its previous value
row-commitEditingReverts the entire row to pre-edit state
column-moveReorderRejects the column drag-and-drop
row-moveRow ReorderRejects the row drag/keyboard move

The DataGridEventMap is the complete, auto-generated reference for every event the grid dispatches — core events and plugin events alike. It includes detail payload types, descriptions, and code examples, grouped by plugin.

See the Theming page for the complete list of CSS custom properties.

tbw-grid {
/* Colors */
--tbw-color-bg: #ffffff;
--tbw-color-fg: #333333;
--tbw-color-border: #e0e0e0;
--tbw-color-accent: #1976d2;
/* Header */
--tbw-color-header-bg: #f5f5f5;
--tbw-color-header-fg: #333333;
/* Rows */
--tbw-color-row-hover: #f0f7ff;
--tbw-color-selection: #e3f2fd;
/* Sizing (em-based for proportional scaling) */
--tbw-row-height: 1.75em;
--tbw-header-height: 1.875em;
}

Apply dynamic CSS classes to entire rows based on row data:

import type { GridConfig } from '@toolbox-web/grid';
const config: GridConfig<Employee> = {
rowClass: (row) => {
const classes: string[] = [];
if (!row.active) classes.push('row-inactive');
if (row.priority === 'high') classes.push('row-priority');
if (row.role === 'admin') classes.push('row-admin');
return classes;
},
};

Apply dynamic CSS classes to individual cells:

import type { ColumnConfig } from '@toolbox-web/grid';
const columns: ColumnConfig<Employee>[] = [
{
field: 'score',
header: 'Score',
cellClass: (value, row, column) => {
if (value < 30) return ['score-low'];
if (value > 70) return ['score-high'];
return ['score-medium'];
},
},
{
field: 'status',
header: 'Status',
cellClass: (value) => [`status-${value}`], // e.g., 'status-active', 'status-pending'
},
];

Notes:

  • Both callbacks return string[] (array of class names)
  • Empty array [] means no dynamic classes
  • Classes are automatically cleaned up when data changes
  • Errors in callbacks are caught and logged (rendering continues)

Define formatters, renderers, and editors at the type level, applying to all columns with matching type.

const config: GridConfig<Employee> = {
typeDefaults: {
// Formatter — transforms value to display string
currency: {
format: (value) => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(value as number),
},
// Renderer — creates custom DOM
country: {
renderer: (ctx) => {
const span = document.createElement('span');
span.textContent = `🌍 ${ctx.value}`;
return span;
},
},
},
columns: [
{ field: 'salary', type: 'currency' }, // Uses currency formatter
{ field: 'country', type: 'country' }, // Uses country renderer
],
};

Resolution Priority: Column-level → Grid typeDefaults → App-level (framework adapter) → Built-in

Configure grid-level animation behavior.

ModeBehavior
true / 'on'Animations always enabled
false / 'off'Animations always disabled
'reduced-motion'Respects prefers-reduced-motion media query (default)
grid.gridConfig = {
animation: {
mode: 'on',
duration: 300,
easing: 'ease-in-out',
},
plugins: [
new ReorderPlugin({ animation: 'flip' }), // 'flip' | 'fade' | false
new MasterDetailPlugin({ animation: 'slide' }), // 'slide' | 'fade' | false
new TreePlugin({ animation: 'fade' }), // 'slide' | 'fade' | false
],
};

Animation timing is exposed via CSS:

tbw-grid {
--tbw-animation-duration: 200ms;
--tbw-animation-easing: ease-out;
--tbw-animation-enabled: 1; /* 0 = disabled, 1 = enabled */
}

Configure the grid’s header bar and tool panel.

grid.gridConfig = {
shell: {
header: {
title: 'Employee Directory',
},
toolPanel: {
position: 'right', // 'left' | 'right'
width: 280, // Panel width in pixels
defaultOpen: 'filters', // Panel ID to open initially
persistState: true, // Persist open/closed state
closeOnClickOutside: true, // Close when clicking outside panel
},
},
};

Override any grid icon with SVG strings or emoji.

grid.gridConfig = {
icons: {
sortAsc: '',
sortDesc: '',
expand: '+',
collapse: '',
},
};

The grid follows the W3C WAI-ARIA Grid Pattern and includes built-in accessibility features.

Provide accessible names for screen reader users:

grid.gridConfig = {
gridAriaLabel: 'Employee directory',
gridAriaDescribedBy: 'grid-description', // ID of element with description
};

If gridAriaLabel is not set but shell.header.title is configured, the title is used automatically as the accessible name.

The grid automatically provides:

  • role="grid" on the data container
  • role="row" and role="gridcell" on rows and cells
  • role="columnheader" on header cells
  • aria-rowcount and aria-colcount for total counts
  • aria-rowindex and aria-colindex on each cell
  • aria-sort on sortable headers
  • aria-selected on focused/selected cells
  • aria-readonly on non-editable cells (when EditingPlugin is active)
  • aria-checked on boolean cells

See the full keyboard shortcut reference in the Accessibility guide.

The grid uses standard Web Components APIs and is compatible with:

  • Chrome/Edge 79+
  • Firefox 63+
  • Safari 13.1+

No polyfills required for modern browsers.

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