Your First Grid
Step-by-step tutorial building a full-featured data grid with columns, renderers, and events.
Build a full-featured grid with the current Grid constructor, typed columns, array data, renderers, and events.
Step 1: Container
<div id="grid-container"></div><script type="module" src="/src/main.ts"></script>#grid-container { width: 100%; height: 600px; border: 1px solid #d1d5db;} ⚠ Warning
The grid container must have explicit width and height before you call render().
Step 2: Columns
import type { ColumnDef } from '@zengrid/core';
export const columns: ColumnDef[] = [ { field: 'id', header: 'ID', width: 80, sortable: true }, { field: 'name', header: 'Name', width: 200, sortable: true, editable: true }, { field: 'email', header: 'Email', width: 260, filterable: true }, { field: 'department', header: 'Department', width: 180, filterable: true }, { field: 'salary', header: 'Salary', width: 120, renderer: 'currency', sortable: true }, { field: 'active', header: 'Active', width: 100, renderer: 'checkbox', editable: true },];Step 3: Data
Keep application records typed, then map them to a row matrix for grid.setData().
export interface Employee { id: number; name: string; email: string; department: string; salary: number; active: boolean;}
export const employees: Employee[] = [ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', department: 'Engineering', salary: 95000, active: true }, { id: 2, name: 'Bob Smith', email: 'bob@example.com', department: 'Sales', salary: 75000, active: true }, { id: 3, name: 'Charlie Brown', email: 'charlie@example.com', department: 'Marketing', salary: 68000, active: false },];
export const rows = employees.map((employee) => [ employee.id, employee.name, employee.email, employee.department, employee.salary, employee.active,]);Step 4: Create The Grid
import { Grid } from '@zengrid/core';import '@zengrid/core/dist/styles.css';import { columns } from './columns';import { rows } from './data';
const grid = new Grid(document.getElementById('grid-container')!, { columns, rowCount: rows.length, colCount: columns.length, rowHeight: 40, colWidth: columns.map((column) => column.width ?? 140), enableSelection: true, enableMultiSelection: true, selectionType: 'range', enableKeyboardNavigation: true,});
grid.setData(rows);grid.render();Step 5: Custom Renderer
import type { CellRenderer } from '@zengrid/core';
export const currencyRenderer: CellRenderer = { render(element, { value }) { element.textContent = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(Number(value)); }, update(element, params) { this.render(element, params); }, destroy(element) { element.textContent = ''; },};Register the renderer before the first render.
import { currencyRenderer } from './renderers';
grid.registerRenderer('currency', currencyRenderer);grid.render();Step 6: Events And APIs
grid.on('cell:click', ({ cell, value }) => { console.log(`Clicked row ${cell.row}, column ${cell.col}:`, value);});
grid.on('cell:doubleClick', ({ cell }) => { console.log('Double clicked', cell);});
grid.on('selection:change', ({ ranges }) => { console.log('Selected ranges:', ranges);});
grid.on('edit:commit', ({ cell, newValue }) => { rows[cell.row][cell.col] = newValue;});
grid.on('sort:change', ({ sortState }) => { console.log('Sort state:', sortState);});
grid.on('filter:change', ({ filterState }) => { console.log('Filter state:', filterState);});
grid.sort.toggle(1);grid.filter.set(2, 'contains', '@example.com');grid.columns.autoFitAll(); ℹ Info
Use current event names such as cell:doubleClick, edit:commit, sort:change, filter:change, and column:resize.
Cleanup
window.addEventListener('beforeunload', () => { grid.destroy();});In framework wrappers, call destroy() from the component unmount hook.
Complete Example
import { Grid, type CellRenderer, type ColumnDef } from '@zengrid/core';import '@zengrid/core/dist/styles.css';
const columns: ColumnDef[] = [ { field: 'id', header: 'ID', width: 80, sortable: true }, { field: 'name', header: 'Name', width: 200, sortable: true, editable: true }, { field: 'email', header: 'Email', width: 260, filterable: true }, { field: 'salary', header: 'Salary', width: 120, renderer: 'currency' },];
const rows = [ [1, 'Alice', 'alice@example.com', 95000], [2, 'Bob', 'bob@example.com', 75000], [3, 'Charlie', 'charlie@example.com', 68000],];
const currencyRenderer: CellRenderer = { render(element, { value }) { element.textContent = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(Number(value)); }, update(element, params) { this.render(element, params); }, destroy(element) { element.textContent = ''; },};
const grid = new Grid(document.getElementById('grid-container')!, { columns, rowCount: rows.length, colCount: columns.length, rowHeight: 40, colWidth: columns.map((column) => column.width ?? 140), enableSelection: true, selectionType: 'range',});
grid.registerRenderer('currency', currencyRenderer);grid.setData(rows);grid.render();Next Steps
Learn about TypeScript Setup for stricter typing and reusable helpers.