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.
Security
Section titled “Security”Sanitize User-Supplied Data
Section titled “Sanitize User-Supplied Data”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
typeexpectations (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
Avoid innerHTML in Custom Renderers
Section titled “Avoid innerHTML in Custom Renderers”Cell renderers return DOM elements, which is safe by default. The risk comes from innerHTML:
// ✅ Safe — textContent escapes HTML automaticallyrenderer: (ctx) => { const span = document.createElement('span'); span.textContent = ctx.value; return span;};
// ❌ XSS vulnerability — user input rendered as HTMLrenderer: (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.
Content Security Policy (CSP)
Section titled “Content Security Policy (CSP)”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.
Performance
Section titled “Performance”Import Only What You Use
Section titled “Import Only What You Use”Each feature import adds to your bundle. Only import the features your app actually needs:
// ✅ Tree-shakeable — only selection and filtering bundledimport '@toolbox-web/grid/features/selection';import '@toolbox-web/grid/features/filtering';
// ❌ Imports all 22 plugins — no tree-shakingimport '@toolbox-web/grid/all';Target sizes: Core is ≤170 kB (≤45 kB gzipped). Each plugin adds 2–15 kB.
Use Fixed Row Heights
Section titled “Use Fixed Row Heights”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
undefinedonly for rows that truly need measurement. - 50+ columns — Consider enabling the
columnVirtualizationfeature 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.
Handle Data Loading
Section titled “Handle Data Loading”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;}Error Handling
Section titled “Error Handling”Plugin Configuration Errors
Section titled “Plugin Configuration Errors”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);}Accessibility
Section titled “Accessibility”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.
Pre-Launch Verification
Section titled “Pre-Launch Verification”- Tab to the grid — Focus should land on the first data cell
- Arrow keys — Navigate to all cells
- Enter on a column header — Should toggle sort
- Enter on an editable cell — Should open the editor; Escape should cancel
- Screen reader — Test with NVDA (Windows) or VoiceOver (macOS). Column headers and sort state should be announced.
See the Accessibility Guide for full details.
Testing
Section titled “Testing”Unit Tests
Section titled “Unit Tests”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); });});E2E Tests (Playwright / Cypress)
Section titled “E2E Tests (Playwright / Cypress)”- 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.
Monitoring
Section titled “Monitoring”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 });});Consumer Deployment Checklist
Section titled “Consumer Deployment Checklist”- External data sanitized before assigning to
grid.rows - No
innerHTMLfrom user input in custom renderers - CSP headers include
style-src 'self' - Only needed features imported (check bundle size)
- Fixed
rowHeightset for large datasets - Data fetch failures handled with loading states
- Keyboard navigation verified
- Screen reader tested
- Error handling around grid configuration
For Plugin & Adapter Developers
Section titled “For Plugin & Adapter Developers”The sections above cover application developers. If you’re building custom plugins or framework adapters, these additional concerns apply.
Plugin Render Performance
Section titled “Plugin Render Performance”Every plugin hook runs in the grid’s hot path. Be aware of the cost:
| Hook | When it runs | Impact |
|---|---|---|
processColumns() | Every data/config update | Low — runs once per update |
processRows() | Every data update | Medium — runs over full dataset |
afterCellRender() | Every visible cell, every scroll | High — keep < 0.1ms per call |
afterRowRender() | Every visible row, every scroll | High — keep < 0.1ms per call |
afterRender() | Every render cycle | Medium — 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.
Style Injection
Section titled “Style Injection”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 CSSthis.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 renderconst style = document.createElement('style');this.gridElement.appendChild(style);Framework Adapter Considerations
Section titled “Framework Adapter Considerations”- 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, andloadingRendererto framework-native components (JSX, Vue render functions, Angular component classes) - Cleanup — Framework adapters that use
createRoot(React) orcreateApp(Vue) must clean up on cell recycle and grid disconnect
See the Custom Plugins Guide for the full plugin development reference.