Skip to content

Production Checklist

This checklist is for application developers shipping <tbw-grid> to end users. Plugin authors and framework adapter developers have additional concerns covered at the bottom.


The grid renders whatever you pass in rows. If your data comes from an API, database, or user input, validate it before assigning:

  • Validate types — Ensure values match your column type expectations (strings where expected, numbers where expected)
  • Ensure unique row IDs — If you use getRowId, duplicate IDs cause unpredictable behavior
  • Strip unexpected fields — Don’t blindly pass API responses with extra properties you didn’t intend to display

Cell renderers return DOM elements, which is safe by default. The risk comes from innerHTML:

// ✅ Safe — textContent escapes HTML automatically
renderer: (ctx) => {
const span = document.createElement('span');
span.textContent = ctx.value;
return span;
};
// ❌ XSS vulnerability — user input rendered as HTML
renderer: (ctx) => {
const div = document.createElement('div');
div.innerHTML = ctx.value; // If value contains <script>, it executes!
return div;
};

How to fix: Replace innerHTML with textContent. If you genuinely need HTML, use a sanitization library (e.g., DOMPurify) before assigning to innerHTML.

The grid injects styles via adoptedStyleSheets. Your CSP must include:

style-src 'self';

If styles aren’t applying in production, see CSP Violations in Troubleshooting for diagnosis steps.


Each feature import adds to your bundle. Only import the features your app actually needs:

// ✅ Tree-shakeable — only selection and filtering bundled
import '@toolbox-web/grid/features/selection';
import '@toolbox-web/grid/features/filtering';
// ❌ Imports all 22 plugins — no tree-shaking
import '@toolbox-web/grid/all';

Target sizes: Core is ≤170 kB (≤45 kB gzipped). Each plugin adds 2–15 kB.

The grid virtualizes rows by default. Fixed heights give the best scroll performance because the grid can calculate positions with pure math:

grid.gridConfig = {
rowHeight: 36, // Fixed height — fastest option
};

When to worry:

  • 10K+ rows with variable heights — Auto-measurement adds a DOM read pass after each render. Prefer fixed heights or return undefined only for rows that truly need measurement.
  • 50+ columns — Consider enabling the columnVirtualization feature to avoid rendering off-screen columns.
  • Complex cell renderers — Renderers run on every scroll frame for visible cells. Keep them fast — avoid network calls, heavy computation, or deep DOM trees.

If you hit scroll stuttering or blank rows at runtime, see Performance Issues and Virtualization Issues in Troubleshooting.

Show loading state during async data fetches and handle failures gracefully:

try {
grid.loading = true;
const data = await fetchEmployees();
grid.rows = data;
} catch (error) {
grid.rows = [];
showNotification('Failed to load data');
} finally {
grid.loading = false;
}

The grid validates your configuration at runtime. If you use a column property that requires a plugin (e.g., editable without the editing feature), the grid throws an error with an import hint:

[tbw-grid] Configuration error:
Column(s) [name, email] use the "editable" column property, but the required plugin is not loaded.
→ import '@toolbox-web/grid/features/editing';

How to fix: Follow the import suggestion in the error message. These errors should be caught during development — if they reach production, wrap grid setup in try/catch as a safety net. For more plugin debugging steps, see Plugin Issues in Troubleshooting.

try {
grid.gridConfig = config;
} catch (error) {
console.error('Grid configuration error:', error.message);
}

The grid includes built-in accessibility features — keyboard navigation, ARIA attributes, focus management, and screen reader support. You don’t need to add these yourself, but you should verify they work in your deployment.

  1. Tab to the grid — Focus should land on the first data cell
  2. Arrow keys — Navigate to all cells
  3. Enter on a column header — Should toggle sort
  4. Enter on an editable cell — Should open the editor; Escape should cancel
  5. Screen reader — Test with NVDA (Windows) or VoiceOver (macOS). Column headers and sort state should be announced.

See the Accessibility Guide for full details.


Use Vitest with happy-dom:

import { describe, it, expect, afterEach } from 'vitest';
import '@toolbox-web/grid';
describe('Employee Grid', () => {
afterEach(() => {
document.body.innerHTML = '';
});
it('should render rows', async () => {
const grid = document.createElement('tbw-grid');
document.body.appendChild(grid);
grid.rows = [{ name: 'Alice' }, { name: 'Bob' }];
await new Promise(resolve => setTimeout(resolve, 0));
const rows = grid.querySelectorAll('[data-row-index]');
expect(rows.length).toBe(2);
});
});
  • Wait for grid readiness — The grid renders asynchronously. Wait for [data-row-index] elements before asserting.
  • Virtual scrolling — Only visible rows exist in the DOM. Scroll to bring off-screen rows into view before interacting.
  • Editing — Double-click a cell, wait for the input element, type, then press Enter.

Track grid interactions for performance and usage analytics:

grid.on('sort-change', ({ column }) => {
analytics.track('grid_sort', { column });
});
grid.on('filter-change', ({ filterModel }) => {
analytics.track('grid_filter', { model: filterModel });
});

  • External data sanitized before assigning to grid.rows
  • No innerHTML from user input in custom renderers
  • CSP headers include style-src 'self'
  • Only needed features imported (check bundle size)
  • Fixed rowHeight set for large datasets
  • Data fetch failures handled with loading states
  • Keyboard navigation verified
  • Screen reader tested
  • Error handling around grid configuration

The sections above cover application developers. If you’re building custom plugins or framework adapters, these additional concerns apply.

Every plugin hook runs in the grid’s hot path. Be aware of the cost:

HookWhen it runsImpact
processColumns()Every data/config updateLow — runs once per update
processRows()Every data updateMedium — runs over full dataset
afterCellRender()Every visible cell, every scrollHigh — keep < 0.1ms per call
afterRowRender()Every visible row, every scrollHigh — keep < 0.1ms per call
afterRender()Every render cycleMedium — avoid DOM queries

How to fix slow plugins: Profile with the browser’s Performance tab. Look for your plugin name in the flame chart during scroll. Move expensive work out of per-cell hooks into processRows() or cache results.

Plugins must not append <style> elements as children of <tbw-grid> (they get removed by replaceChildren()). Standard CSS and the plugin styles property both work:

// ✅ Use the styles property (recommended for plugins)
override readonly styles = `
.my-class { color: blue; }
`;
// ✅ Or registerStyles for runtime-injected CSS
this.gridElement.registerStyles('my-id', '.my-class { color: blue; }');
// ✅ Standard CSS also works (global stylesheet, <style> in <head>)
// .my-class { color: blue; }
// ❌ Don't do this — child nodes are removed on render
const style = document.createElement('style');
this.gridElement.appendChild(style);
  • Re-export features — Each adapter must re-export feature side-effect imports so consumers can import '@toolbox-web/grid-react/features/selection'
  • Renderer bridging — Ensure your adapter bridges renderer, headerRenderer, and loadingRenderer to framework-native components (JSX, Vue render functions, Angular component classes)
  • Cleanup — Framework adapters that use createRoot (React) or createApp (Vue) must clean up on cell recycle and grid disconnect

See the Custom Plugins Guide for the full plugin development reference.


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