# API Reference

> Complete reference for the <tbw-grid> component — properties, methods, events, CSS custom properties, keyboard shortcuts, declarative configuration, and accessibility.

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

## Element Tag

```html
<tbw-grid></tbw-grid>
```

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

## Properties

### Core Data Properties

| Property     | Type             | Default | Description                |
| ------------ | ---------------- | ------- | -------------------------- |
| `rows`       | `T[]`            | `[]`    | Array of row data objects  |
| `columns`    | [`ColumnConfig[]`](/grid/api/core/interfaces/columnconfig.md) | `[]`    | Column configuration array |
| `gridConfig` | [`GridConfig<T>`](/grid/api/core/interfaces/gridconfig.md)  | `{}`    | Full configuration object  |

### Layout Properties

| Property  | Type                   | Default     | Description            |
| --------- | ---------------------- | ----------- | ---------------------- |
| `fitMode` | `'stretch' \| 'fixed'` | `'stretch'` | Column sizing strategy |

### State Properties

| Property      | Type                        | Default | Description                                 |
| ------------- | --------------------------- | ------- | ------------------------------------------- |
| `loading`     | `boolean`                   | `false` | Grid-level loading state. Customise the indicator via [`gridConfig.loadingRenderer`](/grid/api/core/types/loadingrenderer.md) |
| `columnState` | [`GridColumnState[]`](/grid/api/core/interfaces/gridcolumnstate.md)         | -       | Get/set column widths, order, visibility, sort |

### Read-only Properties

| Property      | Type                         | Description                   |
| ------------- | ---------------------------- | ----------------------------- |
| `changedRows` | `T[]`                        | Rows with pending edits (requires EditingPlugin) |
| `sortState`   | `Map<string, `[`SortState`](/grid/api/core/interfaces/sortstate.md)`>`     | Current sort state per column |

## Declarative Configuration

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).

:::note
JavaScript property assignment always takes precedence over declarative configuration. Removing an attribute or element does not clear a JS-set value.
:::

### HTML Attributes

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

| Attribute     | Maps to Property | Type   | Description                |
| ------------- | ---------------- | ------ | -------------------------- |
| `rows`        | `rows`           | JSON   | Row data as JSON array     |
| `columns`     | `columns`        | JSON   | Column config as JSON array|
| `grid-config` | `gridConfig`     | JSON   | Full config object as JSON |
| `fit-mode`    | `fitMode`        | string | `'stretch'` or `'fixed'`   |

```html
<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>
```

### Plugin Host Attributes (`data-*`)

Some plugins read their own `data-*` attributes directly from the `<tbw-grid>` host
element when they attach. These are namespaced under `data-` to avoid colliding with
the grid's own reactive attributes (`rows`, `columns`, `grid-config`, `fit-mode`,
`loading`). They are read **once at attach time** — changing them afterward has no
effect (use the JSON `grid-config` attribute or a JS property for reactive updates).

| Attribute  | Owning plugin      | Description                                                                                         |
| ---------- | ------------------ | -------------------------------------------------------------------------------------------------- |
| `data-src` | [`ServerSidePlugin`](/grid/plugins/server-side.md) | URL to fetch row data from — enables a zero-JS server-fetched grid. See [Server-Side → Declarative `data-src`](/grid/plugins/server-side.md#declarative-data-src-zero-js). |

### Light DOM Elements

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.

#### Column Elements

##### `<tbw-grid-column>`

Define column configuration declaratively.

| Attribute      | Type      | Description                                                      |
| -------------- | --------- | ---------------------------------------------------------------- |
| `field`        | `string`  | **Required.** Data field key                                     |
| `header`       | `string`  | Column header text                                               |
| `type`         | `string`  | Column type. Built-ins: `'string'`, `'number'`, `'boolean'`, `'date'`, `'select'`. Any custom/plugin-registered type string is also accepted. |
| `width`        | `string`  | Column width (e.g., `"120"`, `"100px"`, `"20%"`)                 |
| `min-width`    | `number`  | Minimum column width in pixels                                   |
| `sortable`     | `boolean` | Enable sorting (presence = true)                                 |
| `resizable`    | `boolean` | Enable resizing (presence = true)                                |
| `order`        | `number`  | Initial column position (0-based index; must be non-negative integer). Columns without `order` fill positions in declaration order. Only affects initial render; user reorder takes precedence. |
| `editable`     | `boolean` | Enable editing (presence = true, requires EditingPlugin)         |
| `options`      | `string`  | Select options: `"value1:Label1,value2:Label2"` or `"val1,val2"` |
| `pinned`       | `string`  | Pin the column to an edge: `'left'` / `'start'` or `'right'` / `'end'` (requires PinnedColumnsPlugin). |
| `hidden`       | `boolean` | Hide the column initially (presence = true; `hidden="false"` keeps it visible). Requires VisibilityPlugin. |
| `lock-visible` | `boolean` | Prevent the column from being hidden via the visibility panel (presence = true). Requires VisibilityPlugin. |

> Plugin-contributed attributes (`pinned`, `hidden`, `lock-visible`, `editable`, `<tbw-grid-column-editor>`) are read by their owning plugin when it is registered. They set the column's **initial** state — subsequent runtime changes (e.g. `setColumnVisible()`, drag-to-pin) take precedence.

```html
<tbw-grid>
  <tbw-grid-column field="id" header="ID" type="number" width="80" order="0"></tbw-grid-column>
  <tbw-grid-column field="name" header="Name" sortable resizable order="1"></tbw-grid-column>
  <tbw-grid-column field="role" type="select" options="admin:Admin,user:User" order="2"></tbw-grid-column>
</tbw-grid>
```

##### `<tbw-grid-column-view>`

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

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

##### `<tbw-grid-column-editor>`

Custom editor template inside a column (requires EditingPlugin).

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

##### `<tbw-grid-column-header>`

Custom header template inside a column.

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

#### Shell Elements

##### `<tbw-grid-header>`

Configure the shell header bar.

| Attribute | Type     | Description              |
| --------- | -------- | ------------------------ |
| `title`   | `string` | Grid title (left side)   |

```html
<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>
```

##### `<tbw-grid-header-content>`

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

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

##### `<tbw-grid-tool-buttons>`

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

```html
<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>
```

#### Tool Panel Elements

##### `<tbw-grid-tool-panel>`

Define a custom tool panel for the sidebar.

| Attribute | Type     | Description                                    |
| --------- | -------- | ---------------------------------------------- |
| `id`      | `string` | **Required.** Unique panel identifier          |
| `title`   | `string` | **Required.** Panel title in accordion header  |
| `icon`    | `string` | Icon for accordion header (emoji or text)      |
| `tooltip` | `string` | Tooltip for accordion header                   |
| `order`   | `number` | Panel order priority (lower = first, default: 100) |

```html
<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>
```

#### Plugin-Specific Elements

##### `<tbw-grid-detail>` (MasterDetailPlugin)

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

| Attribute             | Type                          | Description                                          |
| --------------------- | ----------------------------- | ---------------------------------------------------- |
| `animation`           | `'slide' \| 'fade' \| false` | Panel expand/collapse animation (default: `'slide'`) |
| `show-expand-column`  | `boolean`                     | Show expand/collapse column (default: `true`)        |
| `expand-on-row-click` | `boolean`                     | Expand when row clicked (default: `false`)           |
| `height`              | `number \| 'auto'`           | Panel height in pixels or auto (default: `'auto'`)   |

```html
<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](/grid/plugins/master-detail.md) for more details.

##### `<tbw-grid-responsive-card>` (ResponsivePlugin)

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

| Attribute         | Type                 | Description                                             |
| ----------------- | -------------------- | ------------------------------------------------------- |
| `breakpoint`      | `number`             | Width threshold in pixels for responsive mode           |
| `card-row-height` | `number \| 'auto'`   | Card height in pixels or auto (default: `'auto'`)       |
| `hidden-columns`  | `string`             | Comma-separated field names to hide in card mode        |
| `hide-header`     | `boolean`            | Hide header row in responsive mode (default: `true`)    |
| `debounce-ms`     | `number`             | Resize debounce delay in ms (default: `100`)            |

```html
<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](/grid/plugins/responsive.md) for more details.

:::note
Framework adapters may provide wrapper components for these elements. For example, `@toolbox-web/grid-react` provides `<GridResponsiveCard>` and `<GridDetailPanel>` React components with render prop support. See the [React adapter docs](/grid/react/getting-started.md) for details.
:::

## Helper Functions

```typescript
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;
}
```

## Methods

### Data Methods

```typescript
// 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();
```

### Row Update API

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.

#### Row Identification

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

:::note
Rows without any of these will not be accessible via the Row Update API. Calls to `getRow()` or `updateRow()` for such rows will return `undefined` or have no effect.
:::

```typescript
// 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"
```

#### Reading Rows

```typescript
// 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
```

#### Updating Rows

```typescript
// 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');
```

#### Listening to Row Updates

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

```typescript
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
});
```

:::note
The `cell-change` event is distinct from `cell-commit`, which fires during inline editing. Use `cell-change` for programmatic updates via `updateRow()`/`updateRows()`.
:::

### Column Methods

```typescript
// 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();
```

### Editing Methods

Bulk-edit lifecycle, change tracking, and active-row controls are provided by the **Editing plugin** and surfaced on the grid instance when the plugin is loaded. See [Editing Plugin → Imperative Bulk-Edit API](/grid/plugins/editing.md#imperative-bulk-edit-api) for the full method list and examples.

### 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)         |

`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'`).

```typescript
// 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();
}
```

### Custom Styles API

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`:

```typescript
// Inject styles programmatically at runtime
grid.registerStyles('my-id', '.my-class { color: blue; }');

// Remove when no longer needed
grid.unregisterStyles('my-id');

// List currently registered style IDs
const styleIds = grid.getRegisteredStyles();
```

:::caution
Do not place `<style>` elements as **children of `<tbw-grid>`** — the grid calls `replaceChildren()` during renders, which removes child nodes. This only affects `<style>` tags placed *inside* the grid element itself. External stylesheets, `<style>` in `<head>`, and `<link>` tags are completely unaffected.
:::

### Insert & Remove Rows

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 `Promise`s that resolve when the animation completes.

```typescript
// Insert a row at visible index 3 (auto-animates)
grid.insertRow(3, newEmployee);

// Insert without animation
grid.insertRow(3, newEmployee, false);
```

```typescript
// Remove with fade-out animation (default) — await to ensure removal
await grid.removeRow(5);

// Remove immediately, no animation
await grid.removeRow(5, false);
```

:::tip
**When to use:** Only for user-initiated row insertion/removal where position matters.
On the next `grid.rows = freshData` assignment, sort and filter re-apply normally.

**When NOT to use:** When receiving fresh data from an API, WebSocket, or any external source. In those cases, just set `grid.rows = newData` and let the grid re-sort and re-filter.
:::

### Row Animation API

All animation methods return `Promise`s that resolve when the animation completes.

```typescript
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');
```

### Loading States

```typescript
// Grid-level loading
grid.loading = true;
grid.loading = false;

// To customise the loading indicator, configure a renderer at config time:
grid.gridConfig = {
  loadingRenderer: (ctx) => {
    const el = document.createElement('div');
    el.textContent = ctx.size === 'large' ? 'Loading…' : '…';
    return el;
  },
};

// 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);
```

### Column State Persistence

```typescript
// 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.applyColumnState(JSON.parse(saved));
}

// Reset to initial configuration
grid.resetColumnState();
```

### Shell Methods (Header/Toolbar)

The header bar, toolbar content, and tool panels are owned by the built-in **shell plugin**. Access it via `grid.getPluginByName('shell')` rather than the deprecated `grid.*` element delegates (removed in v3):

```typescript
const shell = grid.getPluginByName('shell');

// Register a tool panel
shell?.registerToolPanel({
  id: 'myPanel',
  title: 'My Panel',
  icon: '⚙️',
  render: (container) => {
    container.innerHTML = '<div>Panel content</div>';
  },
});

// Open/close/toggle the tool panel sidebar
shell?.openToolPanel();
shell?.closeToolPanel();
shell?.toggleToolPanel();

// Open directly on a specific accordion section (one-click navigation)
shell?.openToolPanel('myPanel');

// Toggle specific accordion sections
shell?.toggleToolPanelSection('myPanel');

// Check panel state
const isOpen = shell?.isToolPanelOpen;
const sections = shell?.expandedToolPanelSections;

// Get registered panels
const panels = shell?.getToolPanels();
```

### Header & Toolbar Content

Register custom content in the grid's header bar (above column headers) or toolbar area through the shell plugin:

```typescript
const shell = grid.getPluginByName('shell');

// Register header content (e.g., a search box)
shell?.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
shell?.unregisterHeaderContent('global-search');

// Get all registered header content definitions
const headerContents = shell?.getHeaderContents();

// Register toolbar content (e.g., action buttons)
shell?.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
shell?.unregisterToolbarContent('export-buttons');

// Get all registered toolbar content definitions
const toolbarContents = shell?.getToolbarContents();
```

### Row Height

```typescript
// 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;
```

### Focus Management

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".

```typescript
// 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
```

:::note
**Angular users:** `BaseOverlayEditor` automatically registers/unregisters its panel — no manual calls needed.
:::

### Focus & Navigation API

Programmatic cell focus and scroll-to-row:

```typescript
// 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`:**

| Option     | Type                                          | Default     | Description                      |
| ---------- | --------------------------------------------- | ----------- | -------------------------------- |
| `align`    | `'start' \| 'center' \| 'end' \| 'nearest'`  | `'nearest'` | Where to position the row        |
| `behavior` | `'instant' \| 'smooth'`                       | `'instant'` | Scroll animation                 |

## Events

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

```ts
// CoreEventsDemo.astro
  import '@toolbox-web/grid';
import { queryGrid } from '@toolbox-web/grid';

  const container = document.getElementById('demo-core-events')?.closest('.grid-demo');
  if (container) {
    const grid = queryGrid('tbw-grid', container)!;

    const logEl = container.querySelector<HTMLElement>('[data-event-log]')!;
    let count = 0;

    grid.columns = [
      { field: 'id', header: 'ID', width: 60 },
      { field: 'name', header: 'Name', width: 150 },
      { field: 'role', header: 'Role', width: 130 },
      { field: 'score', header: 'Score', type: 'number', width: 80 },
    ];
    grid.sortable = true;
    grid.resizable = true;

    grid.rows = [
      { id: 1, name: 'Alice', role: 'Engineer', score: 92 },
      { id: 2, name: 'Bob', role: 'Designer', score: 78 },
      { id: 3, name: 'Carol', role: 'Manager', score: 85 },
      { id: 4, name: 'Dan', role: 'Engineer', score: 64 },
      { id: 5, name: 'Eve', role: 'Designer', score: 91 },
    ];

    function logEvent(name: string, detail: unknown) {
      count++;
      const entry = document.createElement('div');
      entry.style.cssText = 'padding:2px 0;border-bottom:1px solid var(--sl-color-gray-5);';
      const summary = typeof detail === 'object' && detail
        ? JSON.stringify(detail).slice(0, 120)
        : String(detail);
      entry.innerHTML = `<span style="color:var(--sl-color-accent)">${count}.</span> <strong>${name}</strong> — ${summary}`;
      logEl.appendChild(entry);
      logEl.scrollTop = logEl.scrollHeight;
    }

    const events = ['cell-click', 'row-click', 'cell-activate', 'sort-change', 'column-resize', 'column-state-change', 'data-change'];
    for (const name of events) {
      grid.on(name, (detail: unknown) => logEvent(name, detail));
    }

    container.querySelector('[data-clear-log]')!.addEventListener('click', () => {
      logEl.innerHTML = '';
      count = 0;
    });
  }
```

### Listening to Events

For framework-specific patterns (vanilla TypeScript, React, Vue, Angular) and the camelCase vs kebab-case binding rules, see [Common Patterns → Event Handling Recipes](/grid/guides/common-patterns.md#listening-to-events-across-frameworks).

### Cancelable Events

Five events support `preventDefault()` to reject the action:

| Event | Plugin | What Canceling Does |
|-------|--------|-------------------|
| `cell-activate` | Core | Suppresses the default activation — prevents plugins (e.g. inline editing) from acting on the click/keypress |
| `cell-commit` | Editing | Reverts the cell to its previous value |
| `row-commit` | Editing | Reverts the entire row to pre-edit state |
| `column-move` | Reorder | Rejects the column drag-and-drop |
| `row-move` | Row Reorder | Rejects the row drag/keyboard move |

`cell-activate` fires **first**, before any plugin handles the interaction, and for **both** pointer clicks and keyboard activation (Enter) — its `detail.trigger` is `'pointer'` or `'keyboard'`. Because it covers the keyboard path and is cancelable, prefer it over `cell-click` (pointer-only) for click-to-open / activate-to-edit patterns that must stay keyboard-accessible.

### Scroll & Render Recipes

The `tbw-scroll` event fires (rAF-batched) on every viewport scroll; the `render` event fires once per render flush after all plugin `afterRender` hooks complete. Cookbook patterns — infinite scroll, sticky progress indicators, lazy cell hydration, post-`addRow` focus, phase-gated render handlers — live in [Common Patterns → Event Handling Recipes](/grid/guides/common-patterns.md#scroll-driven-patterns-tbw-scroll).

For the typed event payloads, see [`DataGridEventMap`](/grid/api/core/interfaces/datagrideventmap.md).

### Event Reference

The [`DataGridEventMap`](/grid/api/core/interfaces/datagrideventmap.md) 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.

- [DataGridEventMap Reference](/grid/api/core/interfaces/datagrideventmap.md): Full list of every event, grouped by plugin, with payload types and code examples.

:::note[Multi-Sort overrides `sort-change`]
When `MultiSortPlugin` is active, the `sort-change` event payload changes from `SortChangeDetail` (single `{ field, direction }`) to `{ sortModel: SortModel[] }` (array of sort columns). This override cannot be expressed in `DataGridEventMap` because TypeScript declaration merging doesn't allow changing a property type. Import `SortModel` from `@toolbox-web/grid/plugins/multi-sort`.
:::

## CSS Custom Properties

The grid exposes ~80 CSS custom properties for theming. See the [Theming guide](/grid/guides/theming.md#css-variable-reference) for the complete, categorised list and the [`GridIcons` interface](/grid/api/core/interfaces/gridicons.md) for the icon variables.

## Configuration Patterns

The patterns that show how to *use* the configuration API live with their respective guides — this section is just a directory:

| Pattern | Where it lives |
|---|---|
| Dynamic `rowClass` / `cellClass` | [Core → Row Styling](/grid/core.md#row-styling) & [Cell Styling](/grid/core.md#cell-styling) |
| Type-level defaults (formatters, renderers, editors) | [Core → Type-Level Defaults](/grid/core.md#type-level-defaults) |
| Row animation (expand/collapse, reorder, insert/remove) | [Core → Row Animation](/grid/core.md#row-animation) and the [`AnimationConfig` interface](/grid/api/core/interfaces/animationconfig.md) |
| Shell layout (header, toolbar, tool panels) | [Shell plugin](/grid/plugins/shell.md) and the [`ShellConfig` interface](/grid/plugins/shell/interfaces/shellconfig.md) |
| Icon customization (CSS + JS paths) | [Theming → Icon Customization](/grid/guides/theming.md#icon-customization) and the [`GridIcons` interface](/grid/api/core/interfaces/gridicons.md) |

## Accessibility & Keyboard

The grid implements the [W3C WAI-ARIA Grid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/) out of the box — ARIA roles (`grid` / `row` / `gridcell` / `columnheader`), `aria-rowindex` / `aria-colindex` / `aria-rowcount` / `aria-colcount`, `aria-sort`, `aria-selected`, `aria-readonly`, and `aria-checked` are applied automatically.

Configure accessible naming via `gridAriaLabel` / `gridAriaDescribedBy` on [`GridConfig`](/grid/api/core/interfaces/gridconfig.md). Customise screen-reader announcements via [`A11yConfig`](/grid/api/core/interfaces/a11yconfig.md).

For the full keyboard shortcut matrix, ARIA reference, screen-reader guidance, focus management, and high-contrast support, see the [Accessibility guide](/grid/guides/accessibility.md).

## Browser Support

Modern evergreen browsers — Chrome/Edge 79+, Firefox 63+, Safari 13.1+. No polyfills required. See the [Production Checklist](/grid/guides/production-checklist.md) for deployment guidance.

## Related Documentation

- [Core Features](/grid/core.md) — Detailed guide on columns, renderers, and formatters
- [Shell plugin](/grid/plugins/shell.md) — Header bar, toolbar, and tool-panel sidebar
- [Theming](/grid/guides/theming.md) — Complete CSS custom property reference and theme customization
- [Getting Started](/grid/getting-started.md) — Installation and quick start guide
- [Demos](/grid/demos.md) — Full-featured demo applications
- [Angular Adapter](/grid/angular/getting-started.md) — `@toolbox-web/grid-angular`
- [React Adapter](/grid/react/getting-started.md) — `@toolbox-web/grid-react`
- [Vue Adapter](/grid/vue/getting-started.md) — `@toolbox-web/grid-vue`
