@toolbox-web/grid
v1.23.2
Published
Zero-dependency, framework-agnostic data grid web component with virtualization, sorting, filtering, editing, and 20+ plugins. Works in vanilla JS, React, Vue, Angular, and any framework.
Downloads
4,401
Maintainers
Readme
@toolbox-web/grid
A high-performance, framework-agnostic data grid built with pure TypeScript and native Web Components. Zero runtime dependencies.
Installation
npm install @toolbox-web/gridQuick Start
import '@toolbox-web/grid';
import { createGrid } from '@toolbox-web/grid';
const grid = createGrid<Employee>({
columns: [
{ field: 'name', header: 'Name' },
{ field: 'email', header: 'Email' },
],
});
grid.rows = data;
document.body.appendChild(grid);Factory Functions
Use createGrid<T>() for typed grid creation (avoids manual casting):
import { createGrid, queryGrid } from '@toolbox-web/grid';
// Create a new typed grid
const grid = createGrid<Employee>({
columns: [{ field: 'name' }],
plugins: [new SelectionPlugin()],
});
// Query an existing grid with proper typing
const existing = queryGrid<Employee>('#my-grid');[!TIP] For complete examples, see the Storybook documentation.
Configuration
The grid supports multiple configuration methods, all converging into a single source of truth (effectiveConfig).
Configuration Methods
1. Via gridConfig (recommended for complex setups):
grid.gridConfig = {
columns: [{ field: 'name' }, { field: 'age', type: 'number' }],
fitMode: 'stretch',
plugins: [new SelectionPlugin({ mode: 'row' })],
shell: { header: { title: 'My Data Grid' } },
};2. Via individual properties (convenience for simple cases):
grid.columns = [{ field: 'name' }, { field: 'age' }];
grid.fitMode = 'stretch';3. Via Light DOM (declarative HTML):
<tbw-grid>
<tbw-grid-column field="name" header="Name" sortable></tbw-grid-column>
<tbw-grid-column field="age" header="Age" type="number"></tbw-grid-column>
<tbw-grid-header title="My Data Grid">
<tbw-grid-header-content>
<span>Custom content</span>
</tbw-grid-header-content>
</tbw-grid-header>
</tbw-grid>Precedence
When the same property is set via multiple methods, higher precedence wins:
- Individual props (
fitMode) - highest columnsprop- Light DOM elements
gridConfigproperty - lowest
Features
API Reference
Element
<tbw-grid></tbw-grid>HTML Attributes
The grid supports configuration via HTML attributes with JSON-serialized values:
| Attribute | Type | Description |
| ------------- | ------ | ------------------------------------------- |
| rows | JSON | Data array (JSON-serialized) |
| columns | JSON | Column definitions (JSON-serialized) |
| grid-config | JSON | Full configuration object (JSON-serialized) |
| fit-mode | string | Column sizing: 'stretch' or 'fixed' |
Example with HTML attributes:
<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>Properties
| Property | Type | Description |
| ------------- | -------------------------------------------------- | -------------------------------------------------- |
| rows | T[] | Data array |
| columns | ColumnConfig[] | Column definitions (→ gridConfig.columns) |
| gridConfig | GridConfig | Full configuration object (single source of truth) |
| fitMode | 'stretch' \| 'fixed' | Column sizing behavior (→ gridConfig.fitMode) |
| loading | boolean \| LoadingContext | Grid-level loading state |
| columnState | GridColumnState[] | Get/set column widths, order, visibility, sort |
| changedRows | T[] (readonly) | Rows with pending edits (requires EditingPlugin) |
| sortState | Map<string, SortState> (readonly) | Current sort state per column |
| focusedCell | { rowIndex, colIndex, field } \| null (readonly) | Currently focused cell position |
Methods
Data Methods
| Method | Returns | Description |
| --------------------------------- | --------------------- | ------------------------------- |
| ready() | Promise<void> | Resolves when fully initialized |
| forceLayout() | Promise<void> | Force re-layout |
| getConfig() | Promise<GridConfig> | Get effective configuration |
| getRowId(row) | string | Get unique identifier for a row |
| getRow(id) | T \| undefined | Get row by its ID |
| updateRow(id, changes, source?) | void | Update a single row by ID |
| updateRows(updates, source?) | void | Batch update multiple rows |
Editing Methods (require EditingPlugin)
| Method | Returns | Description |
| --------------------------- | ------- | ----------------------- |
| beginBulkEdit(rowIndex) | void | Start row editing |
| commitActiveRowEdit() | void | Commit current row edit |
| cancelActiveRowEdit() | void | Cancel current row edit |
| resetChangedRows(silent?) | void | Clear change tracking |
Focus & Navigation Methods
| Method | Returns | Description |
| ---------------------------------- | ------- | ----------------------------------- |
| focusCell(rowIndex, column) | void | Focus a cell by index or field name |
| scrollToRow(rowIndex, options?) | void | Scroll row into view |
| scrollToRowById(rowId, options?) | void | Scroll to row by ID |
Column Methods
| Method | Returns | Description |
| ---------------------------------- | ------------------- | -------------------------------------- |
| setColumnVisible(field, visible) | boolean | Set column visibility |
| toggleColumnVisibility(field) | void | Toggle column visible/hidden |
| isColumnVisible(field) | boolean | Check if column is visible |
| showAllColumns() | void | Show all hidden columns |
| setColumnOrder(order) | void | Reorder columns by field array |
| getColumnOrder() | string[] | Get current column order |
| getAllColumns() | ColumnInfo[] | Get all columns with visibility status |
| getColumnState() | GridColumnState[] | Get column state (for persistence) |
| resetColumnState() | void | Reset to initial column configuration |
Shell Methods
| Method | Returns | Description |
| ------------------------------------- | ----------------------- | ------------------------------------- |
| openToolPanel() | void | Open the tool panel sidebar |
| closeToolPanel() | void | Close the tool panel sidebar |
| toggleToolPanel() | void | Toggle tool panel open/closed |
| toggleToolPanelSection(sectionId) | void | Toggle a tool panel accordion section |
| isToolPanelOpen | boolean (getter) | Whether tool panel is open |
| expandedToolPanelSections | string[] (getter) | IDs of expanded accordion sections |
| getToolPanels() | ToolPanelDefinition[] | Get registered tool panels |
| registerToolPanel(panel) | void | Register a custom tool panel |
| unregisterToolPanel(panelId) | void | Remove a registered tool panel |
| registerHeaderContent(content) | void | Add custom header content |
| unregisterHeaderContent(contentId) | void | Remove custom header content |
| registerToolbarContent(content) | void | Add custom toolbar button |
| unregisterToolbarContent(contentId) | void | Remove custom toolbar button |
Style Methods
| Method | Returns | Description |
| ------------------------- | ---------------------------- | ---------------------------------------- |
| registerStyles(id, css) | void | Register custom CSS (adoptedStyleSheets) |
| unregisterStyles(id) | void | Remove custom CSS by ID |
| getRegisteredStyles() | Map<string, CSSStyleSheet> | Get all registered styles |
Loading Methods
| Method | Returns | Description |
| --------------------------------------- | --------- | -------------------------------- |
| setRowLoading(rowId, loading) | void | Show/hide row loading indicator |
| setCellLoading(rowId, field, loading) | void | Show/hide cell loading indicator |
| isRowLoading(rowId) | boolean | Check if row is loading |
| isCellLoading(rowId, field) | boolean | Check if cell is loading |
| clearAllLoading() | void | Clear all loading states |
Row Mutation Methods
| Method | Returns | Description |
| --------------------------------- | ------------------------- | -------------------------------------------------- |
| insertRow(index, row, animate?) | Promise<void> | Insert a row at a visible position (auto-animates) |
| removeRow(index, animate?) | Promise<T \| undefined> | Remove a row at a visible position (auto-animates) |
Animation Methods
| Method | Returns | Description |
| ---------------------------- | ------------------ | ------------------------------------------- |
| animateRow(index, type) | Promise<boolean> | Animate a single row (change/insert/remove) |
| animateRows(indices, type) | Promise<number> | Animate multiple rows at once |
| animateRowById(id, type) | Promise<boolean> | Animate row by ID (resolves true if found) |
Plugin Methods
| Method | Returns | Description |
| ------------------------ | --------------------- | ------------------------------------------------------------------------- |
| getPluginByName(name) | Plugin \| undefined | Get plugin instance by name — preferred (type-safe, no import needed) |
| getPlugin(PluginClass) | P \| undefined | Get plugin instance by class (requires import) |
Events
Core Events
| Event | Detail | Description |
| ----------------------- | --------------------------- | ------------------------------------------------- |
| cell-click | CellClickDetail | Cell clicked |
| row-click | RowClickDetail | Row clicked |
| cell-activate | CellActivateDetail | Cell activated via keyboard or click (cancelable) |
| cell-change | CellChangeDetail | Row updated via Row Update API |
| sort-change | SortChangeDetail | Sort state changed |
| column-resize | ColumnResizeDetail | Column resized |
| column-state-change | GridColumnState | Column state changed |
| group-toggle | GroupToggleDetail | Row group expanded/collapsed |
| mount-external-view | ExternalMountViewDetail | External view mount request |
| mount-external-editor | ExternalMountEditorDetail | External editor mount request |
Editing Events (require EditingPlugin)
| Event | Detail | Description |
| -------------------- | ------------------------ | --------------------------------- |
| cell-commit | CellCommitDetail | Cell value committed (cancelable) |
| row-commit | RowCommitDetail | Row edit committed (cancelable) |
| edit-open | EditOpenDetail | Row entered edit mode |
| edit-close | EditCloseDetail | Row left edit mode |
| changed-rows-reset | ChangedRowsResetDetail | Change tracking cleared |
| dirty-change | DirtyChangeDetail | Row dirty state changed |
Deprecated Events
| Event | Detail | Description |
| --------------- | -------------------- | ------------------------------------------ |
| activate-cell | ActivateCellDetail | ⚠️ Deprecated. Use cell-activate instead |
Import event names from the DGEvents constant:
import { DGEvents } from '@toolbox-web/grid';
grid.addEventListener(DGEvents.CELL_COMMIT, handler);Insert & Remove Rows
Add or remove rows at a specific visible position without re-running the sort/filter pipeline:
// Insert with animation (default)
grid.insertRow(3, newEmployee);
// Insert without animation
grid.insertRow(3, newEmployee, false);
// Remove with fade-out animation and await completion
await grid.removeRow(5);
// Remove without animation
const removed = await grid.removeRow(5, false);Both methods update the source data automatically. The next full grid.rows = freshData assignment re-sorts/re-filters normally. Do not use them for bulk data refreshes — assign grid.rows directly instead.
Row Animation API
All animation methods return Promises that resolve when the animation completes:
// Flash highlight for updated row
await grid.animateRow(5, 'change');
// Animate by row ID
await grid.animateRowById('emp-123', 'change');
// Animate multiple rows
await grid.animateRows([0, 1, 2], 'change');Note:
insertRowandremoveRowhandle animation automatically — you only needanimateRowdirectly for highlighting existing rows (e.g., after an external data update).
Animation Types: 'change' | 'insert' | 'remove'
Animation Configuration
Configure animation behavior globally:
grid.gridConfig = {
animation: {
mode: 'on', // 'on' | 'off' | 'reduced-motion'
style: 'smooth', // Animation easing (alias for `easing`)
duration: 200, // Default duration in ms
},
};The grid respects prefers-reduced-motion media query automatically when mode is 'reduced-motion'.
Column Configuration
interface ColumnConfig {
field: string; // Required: property key in row data
header?: string; // Display label (defaults to field name)
type?: 'string' | 'number' | 'date' | 'boolean' | 'select';
width?: number | string; // Pixels, '1fr', or percentage
sortable?: boolean; // Enable sorting (default: true)
resizable?: boolean; // Enable resize (default: true)
editable?: boolean; // Enable editing
hidden?: boolean; // Initially hidden
lockVisible?: boolean; // Prevent hiding
format?: (value: any, row: T) => string;
}Plugin-Provided Column Properties
Some column properties are added via TypeScript module augmentation when you import a plugin:
| Property | Plugin | Description |
| ------------- | ----------------- | ------------------------ |
| sticky | pinnedColumns | Pin column left or right |
| group | groupingColumns | Column header group |
| filterable | filtering | Enable column filter |
| filterType | filtering | Filter type |
| reorderable | reorder | Enable column reordering |
See Storybook for complete configuration examples.
Grid Configuration
interface GridConfig {
columns?: ColumnConfig[];
fitMode?: 'stretch' | 'fixed';
plugins?: BaseGridPlugin[]; // Array of plugin class instances
icons?: GridIcons; // Centralized icon configuration
shell?: ShellConfig; // Optional header bar and tool panels
getRowId?: (row: T) => string; // Custom row ID resolver
typeDefaults?: Record<string, TypeDefault<T>>; // Type-level renderers/editors
}Row Identification
The grid uses row IDs for the Row Update API. By default, it looks for id or _id properties on row objects. Rows without an identifiable ID are not accessible via the Row Update API. For custom ID fields, provide a getRowId function:
grid.gridConfig = {
columns: [...],
getRowId: (row) => row.employeeNumber, // Use custom field as ID
};Icons Configuration
The grid provides a centralized icon system via gridConfig.icons. All plugins (tree, grouping, sorting, context menus, etc.) automatically use these icons, ensuring visual consistency across the entire grid.
import { DEFAULT_GRID_ICONS } from '@toolbox-web/grid';
grid.gridConfig = {
icons: {
expand: '▶', // Collapsed tree/group/detail icon
collapse: '▼', // Expanded tree/group/detail icon
sortAsc: '▲', // Sort ascending indicator
sortDesc: '▼', // Sort descending indicator
sortNone: '⇅', // Unsorted column indicator
submenuArrow: '▶', // Context menu submenu arrow
dragHandle: '⋮⋮', // Column reorder drag handle
},
};Icons can be strings (text or HTML) or HTMLElement instances. Plugins use grid-level icons by default but can override with their own config when needed.
Type-Level Defaults
Define renderers and editors at the type level that apply to all columns with matching type:
grid.gridConfig = {
typeDefaults: {
country: {
renderer: (ctx) => {
const span = document.createElement('span');
span.textContent = `🌍 ${ctx.value}`;
return span;
},
editor: (ctx) => {
const select = document.createElement('select');
['USA', 'UK', 'Germany'].forEach((c) => {
const opt = document.createElement('option');
opt.value = c;
opt.textContent = c;
select.appendChild(opt);
});
select.value = ctx.value;
select.onchange = () => ctx.commit(select.value);
return select;
},
},
currency: {
editorParams: { min: 0, step: 0.01 },
},
},
columns: [
{ field: 'country', type: 'country', editable: true }, // Uses country renderer/editor
{ field: 'salary', type: 'currency', editable: true }, // Uses currency editorParams
],
};Resolution Priority: Column-level → gridConfig.typeDefaults → Framework adapter → Built-in
Framework adapters (@toolbox-web/grid-react, @toolbox-web/grid-angular) provide app-level type registries for React/Angular components.
Plugin Configuration Example
Plugins are class instances that you import and instantiate with their configuration:
import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';
import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';
grid.gridConfig = {
plugins: [
new GroupingRowsPlugin({
groupOn: (row) => row.category,
fullWidth: false,
aggregators: { total: 'sum' },
}),
new SelectionPlugin({
mode: 'row',
multiple: true,
}),
],
};Theming
Apply a built-in theme:
@import '@toolbox-web/grid/themes/dg-theme-standard.css';Available themes: standard, contrast, vibrant, large, bootstrap, material
Custom Theming
Override CSS custom properties on tbw-grid or a parent element:
tbw-grid {
--tbw-color-bg: #ffffff;
--tbw-color-fg: #1a1a1a;
--tbw-color-border: #e5e5e5;
--tbw-color-header-bg: #f5f5f5;
--tbw-row-height: 32px;
}For a complete list of available CSS variables, see grid.css.
Core CSS Variables
| Variable | Description |
| ----------------------- | ---------------------------- |
| --tbw-color-bg | Grid background |
| --tbw-color-fg | Text color |
| --tbw-color-fg-muted | Secondary text color |
| --tbw-color-accent | Accent/primary color |
| --tbw-color-border | Border color |
| --tbw-color-header-bg | Header background |
| --tbw-color-header-fg | Header text color |
| --tbw-color-selection | Selected cell/row background |
| --tbw-color-row-hover | Row hover background |
| --tbw-row-height | Data row height |
| --tbw-header-height | Header row height |
| --tbw-font-family | Font family |
| --tbw-font-size | Base font size |
| --tbw-border-radius | Corner radius |
| --tbw-focus-outline | Focus ring style |
Plugin CSS Variables
Plugins define their own CSS variables following a layered fallback pattern:
var(--tbw-{plugin}-{property}, var(--tbw-{global-property}))This allows you to:
- Override a specific plugin's style:
--tbw-selection-bg - Or let it inherit from the global variable:
--tbw-color-selection
Example: Customizing the selection plugin
tbw-grid {
/* Override just the selection plugin's background */
--tbw-selection-bg: #e0f2fe;
/* Or change the global selection color (affects all plugins) */
--tbw-color-selection: #e0f2fe;
}Common plugin variables:
| Plugin | Variables |
| ------------- | ---------------------------------------------------------- |
| selection | --tbw-selection-bg, --tbw-selection-border |
| filtering | --tbw-filtering-panel-bg, --tbw-filtering-input-border |
| contextMenu | --tbw-context-menu-bg, --tbw-context-menu-hover |
| pinnedRows | --tbw-pinned-rows-bg, --tbw-pinned-rows-border |
| tree | --tbw-tree-indent, --tbw-tree-toggle-color |
Check each plugin's styles property for the full list of customizable variables.
Plugins
The grid uses a plugin architecture for optional features. Each plugin has its own documentation:
| Plugin | Description | Documentation | | --------------------- | ------------------------------ | ----------------------------------------------------------- | | Editing | Inline cell editing | README | | Selection | Cell, row, and range selection | README | | Multi-Sort | Multi-column sorting | README | | Filtering | Column filters | README | | Row Grouping | Row grouping with aggregation | README | | Column Grouping | Column header groups | README | | Tree | Tree/hierarchical data | README | | Pivot | Pivot table transformation | README | | Master-Detail | Expandable detail rows | README | | Pinned Columns | Sticky columns | README | | Pinned Rows | Footer aggregations | README | | Reorder | Column drag reordering | README | | Row Reorder | Row drag reordering | README | | Visibility | Column visibility UI | README | | Responsive | Card layout for mobile | README | | Clipboard | Copy/paste | README | | Context Menu | Right-click menus | README | | Export | CSV/Excel/JSON export | README | | Print | Print-optimized layout | README | | Undo/Redo | Edit history | README | | Server-Side | Lazy data loading | README | | Column Virtualization | Horizontal virtualization | README |
Creating Custom Plugins
Plugins extend the BaseGridPlugin class:
import { BaseGridPlugin } from '@toolbox-web/grid';
import styles from './my-plugin.css?inline';
interface MyPluginConfig {
myOption?: boolean;
}
export class MyPlugin extends BaseGridPlugin<MyPluginConfig> {
readonly name = 'myPlugin';
override readonly styles = styles; // CSS imported via Vite
// Default config (override in constructor)
protected override get defaultConfig(): Partial<MyPluginConfig> {
return { myOption: true };
}
// Called when plugin is attached to grid
override attach(grid: GridElement): void {
super.attach(grid);
// Setup event listeners using this.disconnectSignal for auto-cleanup
}
// Called when plugin is detached
override detach(): void {
// Cleanup (listeners with disconnectSignal auto-cleanup)
}
// Hook: Called after grid renders
override afterRender(): void {
// Access DOM via this.gridElement
}
}Inter-Plugin Communication
Plugins can communicate with each other using two systems:
- Event Bus - For async notifications between plugins
- Query System - For sync state retrieval
Event Bus
Emit and subscribe to plugin events (distinct from DOM events):
import { BaseGridPlugin, type PluginManifest } from '@toolbox-web/grid';
export class MyPlugin extends BaseGridPlugin<MyConfig> {
// Declare events this plugin emits
static override readonly manifest: PluginManifest = {
events: [{ type: 'my-event', description: 'Emitted when something happens' }],
};
override attach(grid: GridElementRef): void {
super.attach(grid);
// Subscribe to events from other plugins
this.on('filter-change', (detail) => {
console.log('Filter changed:', detail);
});
}
private doSomething(): void {
// Emit to other plugins (not DOM events)
this.emitPluginEvent('my-event', { data: 'value' });
}
}Query System
Respond to queries from other plugins:
import { BaseGridPlugin, type PluginManifest, type PluginQuery } from '@toolbox-web/grid';
export class MyPlugin extends BaseGridPlugin<MyConfig> {
// Declare queries this plugin handles
static override readonly manifest: PluginManifest = {
queries: [{ type: 'canMoveColumn', description: 'Check if column can be moved' }],
};
override handleQuery(query: PluginQuery): unknown {
if (query.type === 'canMoveColumn') {
const column = query.context as ColumnConfig;
if (this.isLocked(column)) return false;
return undefined; // Let other plugins decide
}
return undefined;
}
}Querying other plugins:
// Simplified API
const responses = grid.query<boolean>('canMoveColumn', column);
const canMove = !responses.includes(false);
// Or full query object
const responses = grid.queryPlugins<boolean>({
type: PLUGIN_QUERIES.CAN_MOVE_COLUMN,
context: column,
});Built-in query types:
| Query Type | Context | Response | Description |
| ------------------------ | ------------------- | ------------------- | ------------------------------- |
| CAN_MOVE_COLUMN | ColumnConfig | boolean | Can the column be reordered? |
| GET_CONTEXT_MENU_ITEMS | ContextMenuParams | ContextMenuItem[] | Get menu items for context menu |
Plugins can also define custom query types for their own inter-plugin communication.
Accessing Plugin Instances
Use grid.getPluginByName() to get a plugin instance for inter-plugin communication or API access:
const selection = grid.getPluginByName('selection');
if (selection) {
selection.selectAll();
}TypeScript
All types are exported from the package:
import type { GridConfig, ColumnConfig, CellCommitDetail, BaseGridPlugin } from '@toolbox-web/grid';Plugin Type Exports
Each plugin exports its class and configuration types from its own entry point:
import { SelectionPlugin, SelectionConfig } from '@toolbox-web/grid/plugins/selection';
import { FilteringPlugin, FilterConfig, FilterModel } from '@toolbox-web/grid/plugins/filtering';
import { TreePlugin, TreeConfig, TreeState } from '@toolbox-web/grid/plugins/tree';All-in-One Bundle
For convenience, you can import everything from the all-in-one bundle:
import {
SelectionPlugin,
FilteringPlugin,
TreePlugin,
// ... all other plugins
} from '@toolbox-web/grid/all';Note: This includes all plugins in your bundle. For smaller bundles, import plugins individually.
Browser Support
Modern browsers with Web Components support (Chrome, Firefox, Safari, Edge).
Development
bun run build
bun run test
bun run coverage
bun run startFor architecture details, rendering pipeline, and plugin development, see ARCHITECTURE.md.
Support This Project
This grid is built and maintained by a single developer in spare time. If it saves you time or money, consider sponsoring to keep development going:
What sponsorship enables:
- 🚀 Faster feature development (see ROADMAP)
- 🐛 Priority bug fixes and support
- 📚 Better documentation and examples
- 💼 Corporate sponsors get logo placement and priority support
License
MIT
