# GridConfig

> _Since v0.1.1_

Grid configuration object - the **single source of truth** for grid behavior.

Users can configure the grid via multiple input methods, all of which converge
into an effective `GridConfig` internally:

**Configuration Input Methods:**
- `gridConfig` property - direct assignment of this object
- `columns` property - shorthand for `gridConfig.columns`
- `fitMode` property - shorthand for `gridConfig.fitMode`
- Light DOM `<tbw-grid-column>` - declarative columns (merged into `columns`)
- Light DOM `<tbw-grid-header>` - declarative shell header (merged into `shell.header`)

**Precedence (when same property set multiple ways):**
Individual props (`fitMode`) &gt; `columns` prop &gt; Light DOM &gt; `gridConfig`

#### Example

```ts
// Via gridConfig (recommended for complex setups)
grid.gridConfig = {
  columns: [{ field: 'name' }, { field: 'age' }],
  fitMode: 'stretch',
  plugins: [new SelectionPlugin()],
  shell: { header: { title: 'My Grid' } }
};

// Via individual props (convenience for simple cases)
grid.columns = [{ field: 'name' }, { field: 'age' }];
grid.fitMode = 'stretch';
```

## Properties

| Property | Type | Description |
| -------- | ---- | ----------- |
| `columns?` | <code><a href="/grid/api/core/types/columnconfigmap/">ColumnConfigMap</a>&lt;TRow&gt;</code> | Column definitions. Can also be set via `columns` prop or `<tbw-grid-column>` light DOM. |
| `rowClass?` | <code>(row: TRow) =&gt; string &#124; string[]</code> | Dynamic CSS class(es) for data rows. Called for each row during rendering. Return class names to add to the row element. |
| `fitMode?` | <code><a href="/grid/api/core/types/fitmode/">FitMode</a></code> | Sizing mode for columns. Can also be set via `fitMode` prop. |
| `columnInference?` | <code><a href="/grid/api/core/types/columninferencemode/">ColumnInferenceMode</a></code> | How automatic column inference combines with explicitly provided columns. Can also be set via the `columnInference` prop or `column-inference` attribute. <span class="since-badge" title="Introduced in v2.17.0">v2.17.0+</span> |
| `sortable?` | <code>boolean</code> | Grid-wide sorting toggle. When false, disables sorting for all columns regardless of their individual `sortable` setting. When true (default), columns with `sortable: true` can be sorted. |
| `resizable?` | <code>boolean</code> | Grid-wide resizing toggle. When false, disables column resizing for all columns regardless of their individual `resizable` setting. When true (default), columns with `resizable: true` (or resizable not set, since it defaults to true) can be resized. |
| `rowHeight?` | <code>number &#124; (row: TRow, index: number) =&gt; number &#124; undefined</code> | Row height in pixels for virtualization calculations. The virtualization system assumes uniform row heights for performance. |
| `plugins?` | <code><a href="/grid/api/plugin-development/interfaces/gridplugin/">GridPlugin</a>[]</code> | Array of plugin instances. Each plugin is instantiated with its configuration and attached to this grid. |
| `features?` | <code>Partial&lt;<a href="/grid/api/core/interfaces/featureconfig/">FeatureConfig</a>&lt;TRow&gt;&gt;</code> | Declarative feature configuration. Alternative to manually creating plugin instances in `plugins`. Features are resolved using the core feature registry. |
| `columnState?` | <code><a href="/grid/api/core/interfaces/gridcolumnstate/">GridColumnState</a></code> | Saved column state to restore on initialization. Includes order, width, visibility, sort, and plugin-contributed state. |
| `icons?` | <code><a href="/grid/api/core/interfaces/gridicons/">GridIcons</a></code> | Grid-wide icon configuration. |
| `animation?` | <code><a href="/grid/api/core/interfaces/animationconfig/">AnimationConfig</a></code> | Grid-wide animation configuration. Controls animations for expand/collapse, reordering, and other visual transitions. Individual plugins can override these defaults in their own config. |
| `sortHandler?` | <code><a href="/grid/api/core/types/sorthandler/">SortHandler</a>&lt;TRow&gt;</code> | Custom sort handler for the entire grid. |
| `initialSort?` | <code>object</code> | Initial sort state applied when the grid first renders. |
| `getRowId?` | <code>(row: TRow) =&gt; string</code> | Function to extract a unique identifier from a row. Used by `updateRow()`, `getRow()`, and ID-based tracking. |
| `typeDefaults?` | <code>Record&lt;string, <a href="/grid/api/core/interfaces/typedefault/">TypeDefault</a>&lt;TRow&gt;&gt;</code> | Type-level renderer and editor defaults. |
| `gridAriaLabel?` | <code>string</code> | Accessible label for the grid. Sets `aria-label` on the grid's internal table element for screen readers. |
| `gridAriaLabelledBy?` | <code>string</code> | ID of an element that labels the grid. Sets `aria-labelledby` on the grid's internal table element so screen readers can use the referenced element's text as the accessible name — useful when the grid already sits next to a heading. |
| `gridAriaDescribedBy?` | <code>string</code> | ID of an element that describes the grid. Sets `aria-describedby` on the grid's internal table element. |
| `gridAriaRoleDescription?` | <code>string</code> | Override the screen-reader-announced role name for the grid via `aria-roledescription`. Useful for localization (e.g. `"Tabell"` in Norwegian) or domain-specific naming (e.g. `"Employee table"`). |
| `a11y?` | <code><a href="/grid/api/core/interfaces/a11yconfig/">A11yConfig</a></code> | Accessibility configuration for screen reader announcements. |
| `loadingRenderer?` | <code><a href="/grid/api/core/types/loadingrenderer/">LoadingRenderer</a></code> | Custom renderer for the loading overlay. |
| `emptyRenderer?` | <code><a href="/grid/api/core/types/emptyrenderer/">EmptyRenderer</a> &#124; unknown</code> | Custom renderer shown when the grid has no rows to display (`loading === false` AND the rendered row count is `0`, after all plugin processing such as filtering / grouping / server-side). <span class="since-badge" title="Introduced in v2.12.0">v2.12.0+</span> |
| `emptyOverlay?` | <code><a href="/grid/api/core/types/emptyoverlay/">EmptyOverlay</a></code> | Where the empty-state overlay is mounted. <span class="since-badge" title="Introduced in v2.12.0">v2.12.0+</span> |
| `editOn?` | <code>false &#124; manual &#124; click &#124; dblclick</code> | Edit trigger mode. Requires `EditingPlugin` to be loaded. |
| `rowEditable?` | <code>(row: TRow) =&gt; boolean</code> | Row-level editability gate. Requires `EditingPlugin` to be loaded. |
| `filterable?` | <code>boolean</code> | Grid-wide filtering toggle. Requires `FilteringPlugin` to be loaded. |
| `columnGroups?` | <code><a href="/grid/plugins/grouping-columns/interfaces/columngroupdefinition/">ColumnGroupDefinition</a>[]</code> | Declarative column group definitions for the GroupingColumnsPlugin. Each group specifies an id, header label, and array of column field names. The plugin will automatically assign the `group` property to matching columns. |
| `selectable?` | <code>boolean</code> | Grid-wide selection toggle. Requires `SelectionPlugin` to be loaded. |
| `shell?` | <code><a href="/grid/plugins/shell/interfaces/shellconfig/">ShellConfig</a></code> | Shell configuration for header bar and tool panels. When configured, adds an optional wrapper with title, toolbar, and collapsible side panels. |

### Property Details

#### columns

**See also:** [`ColumnConfig`](/grid/api/core/interfaces/columnconfig.md) for column options · [`ColumnConfigMap`](/grid/api/core/types/columnconfigmap.md)

---

#### rowClass

```typescript
// Highlight inactive rows
rowClass: (row) => row.active ? [] : ['inactive', 'dimmed']

// Status-based row styling
rowClass: (row) => [`priority-${row.priority}`]

// Single class as string
rowClass: (row) => row.isNew ? 'new-row' : ''
```

---

#### columnInference

How automatic column inference combines with explicitly provided columns.
Can also be set via the `columnInference` prop or `column-inference` attribute.

- `'auto'` (default): infer only when no columns are provided (current behavior).
- `'merge'`: always infer from data, then overlay provided columns by `field`.

**See also:** [`ColumnInferenceMode`](/grid/api/core/types/columninferencemode.md)

---

#### sortable

Grid-wide sorting toggle.
When false, disables sorting for all columns regardless of their individual `sortable` setting.
When true (default), columns with `sortable: true` can be sorted.

This affects:
- Header click handlers for sorting
- Sort indicator visibility
- Multi-sort plugin behavior (if loaded)

**Default:** `true`

```typescript
// Disable all sorting
gridConfig = { sortable: false };

// Enable sorting (default) - individual columns still need sortable: true
gridConfig = { sortable: true };
```

---

#### resizable

Grid-wide resizing toggle.
When false, disables column resizing for all columns regardless of their individual `resizable` setting.
When true (default), columns with `resizable: true` (or resizable not set, since it defaults to true) can be resized.

This affects:
- Resize handle visibility in header cells
- Double-click to auto-size behavior

**Default:** `true`

```typescript
// Disable all column resizing
gridConfig = { resizable: false };

// Enable resizing (default) - individual columns can opt out with resizable: false
gridConfig = { resizable: true };
```

---

#### rowHeight

Row height in pixels for virtualization calculations.
The virtualization system assumes uniform row heights for performance.

If not specified, the grid measures the first rendered row's height,
which respects the CSS variable `--tbw-row-height` set by themes.

Set this explicitly when:
- Row content may wrap to multiple lines (also set `--tbw-cell-white-space: normal`)
- Using custom row templates with variable content
- You want to override theme-defined row height
- Rows have different heights based on content (use function form)

**Variable Row Heights**: When a function is provided, the grid enables variable height
virtualization. Heights are measured on first render and cached by row identity.

**Default:** `Auto-measured from first row (respects --tbw-row-height CSS variable)`

```ts
// Fixed height for all rows
gridConfig = { rowHeight: 56 };

// Variable height based on content
gridConfig = {
  rowHeight: (row, index) => row.hasDetails ? 80 : 40,
};

// Return undefined to trigger DOM auto-measurement
gridConfig = {
  rowHeight: (row) => row.isExpanded ? undefined : 40,
};
```

---

#### plugins

```ts
plugins: [
  new SelectionPlugin({ mode: 'range' }),
  new MultiSortPlugin(),
  new FilteringPlugin({ debounceMs: 150 }),
]
```

---

#### sortHandler

Custom sort handler for the entire grid.

:::caution
**Prefer BaseColumnConfig.sortComparator over `sortHandler`.**

`sortHandler` is a low-level escape hatch with significant limitations:
- Only consulted by the **single-column** sort path (core header click,
  `TreePlugin` per-level sort, `ServerSidePlugin` `sortMode: 'local'`).
- **Bypassed entirely** when `MultiSortPlugin` is loaded.
- Owns ALL columns at once — your handler must implement field/direction
  dispatch and null handling for every sortable column itself.

For per-column custom sort logic, use BaseColumnConfig.sortComparator
instead. It is honored by every sort code path in the grid (core, multi-sort,
tree, server-side) and is composable across columns.

For server-side sort, prefer `ServerSideConfig.dataSource` — the
`sortModel` is shipped to your `getRows` handler so the backend can return
pre-sorted blocks.
:::

Use `sortHandler` only when you need to replace the grid's sort engine
wholesale (e.g. integrating a third-party sort library that operates on
the full row array, or routing every sort through a single async pipeline).

The handler receives:
- `rows`: Current row array to sort
- `sortState`: Sort field and direction (1 = asc, -1 = desc)
- `columns`: Column configurations (for accessing sortComparator)

Return the sorted array (sync) or a Promise that resolves to it (async).

```ts
// Replace the entire client-side sort engine with a custom stable sort
sortHandler: (rows, state) => stableSort(rows, state.field, state.direction);
```

**See also:** [`BaseColumnConfig.sortComparator`](/grid/api/core/interfaces/basecolumnconfig.md#sortcomparator) — recommended per-column override · `ServerSideConfig.dataSource` — recommended server-side sort path

---

#### initialSort

Initial sort state applied when the grid first renders.

Equivalent to calling `grid.sort(field, direction)` after the grid is created,
but avoids the imperative call and extra render cycle.

```ts
gridConfig = {
  initialSort: { field: 'salary', direction: 'desc' },
};
```

**See also:** [`DataGridElement.sort`](/grid/api/core/classes/datagridelement.md#sort) for runtime sorting · [`DataGridElement.sortModel`](/grid/api/core/classes/datagridelement.md#sortmodel) for reading current sort state

---

#### getRowId

Function to extract a unique identifier from a row.
Used by `updateRow()`, `getRow()`, and ID-based tracking.

If not provided, falls back to `row.id` or `row._id` if present.
Rows without IDs are silently skipped during map building.
Only throws when explicitly calling `getRowId()` or `updateRow()` on a row without an ID.

```ts
// Simple field
getRowId: (row) => row.id

// Composite key
getRowId: (row) => `${row.voyageId}-${row.legNumber}`

// UUID field
getRowId: (row) => row.uuid
```

---

#### typeDefaults

Type-level renderer and editor defaults.

Keys can be:
- Built-in types: `'string'`, `'number'`, `'date'`, `'boolean'`, `'select'`
- Custom types: `'currency'`, `'country'`, `'status'`, etc.

Resolution order (highest priority first):
1. Column-level (`column.renderer` / `column.editor`)
2. Grid-level (`gridConfig.typeDefaults[column.type]`)
3. App-level (Angular `GridTypeRegistry`, React `GridTypeProvider`)
4. Built-in (checkbox for boolean, select for select, etc.)
5. Fallback (plain text / text input)

```typescript
typeDefaults: {
  date: { editor: myDatePickerEditor },
  country: {
    renderer: (ctx) => {
      const span = document.createElement('span');
      span.innerHTML = `<img src="/flags/${ctx.value}.svg" /> ${ctx.value}`;
      return span;
    },
    editor: (ctx) => createCountrySelect(ctx)
  }
}
```

---

#### gridAriaLabel

Accessible label for the grid.
Sets `aria-label` on the grid's internal table element for screen readers.

If not provided and `shell.header.title` is set, the title is used automatically.

If [`gridAriaLabelledBy`](#gridarialabelledby) is also set, `aria-labelledby`
takes precedence per WAI-ARIA accessible-name computation and `aria-label`
is omitted.

```ts
gridConfig = { gridAriaLabel: 'Employee data' };
```

---

#### gridAriaLabelledBy

ID of an element that labels the grid.
Sets `aria-labelledby` on the grid's internal table element so screen
readers can use the referenced element's text as the accessible name —
useful when the grid already sits next to a heading.

Per WAI-ARIA accessible-name precedence, `aria-labelledby` takes priority
over `aria-label` and over the auto-derived shell title. When this option
is set, the grid omits `aria-label` to avoid conflicting names.

```html
<h2 id="grid-heading">Employees</h2>
<tbw-grid></tbw-grid>
```
```ts
gridConfig = { gridAriaLabelledBy: 'grid-heading' };
```

---

#### gridAriaDescribedBy

```html
<p id="grid-desc">This table shows all active employees.</p>
<tbw-grid></tbw-grid>
```
```ts
gridConfig = { gridAriaDescribedBy: 'grid-desc' };
```

---

#### gridAriaRoleDescription

Override the screen-reader-announced role name for the grid via
`aria-roledescription`. Useful for localization (e.g. `"Tabell"` in
Norwegian) or domain-specific naming (e.g. `"Employee table"`).

:::caution
Per [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/#aria-roledescription),
the value should still describe a grid-like widget. Overriding with an
unrelated label confuses assistive-technology users about the available
interactions (cell navigation, sort, etc.). Leave unset to use the
default role name announced by the AT.
:::

```ts
gridConfig = { gridAriaRoleDescription: 'Employee table' };
```

---

#### a11y

Accessibility configuration for screen reader announcements.

The grid automatically announces state changes (sort, filter, selection, etc.)
via an `aria-live` region. Use this config to toggle announcements or override
message text for internationalization.

```ts
// Disable all announcements
gridConfig = { a11y: { announcements: false } };

// Custom messages for i18n
gridConfig = {
  a11y: {
    messages: {
      sortApplied: (col, dir) => `Trié par ${col}, ${dir}`,
      filterApplied: (col) => `Filtre appliqué sur ${col}`,
    },
  },
};
```

---

#### loadingRenderer

Custom renderer for the loading overlay.

When provided, replaces the default spinner with custom content.
Receives a context object with the current loading size.

```typescript
// Simple text loading indicator
loadingRenderer: () => {
  const el = document.createElement('div');
  el.textContent = 'Loading...';
  return el;
}

// Custom spinner component
loadingRenderer: (ctx) => {
  const spinner = document.createElement('my-spinner');
  spinner.size = ctx.size === 'large' ? 48 : 24;
  return spinner;
}
```

---

#### emptyRenderer

Custom renderer shown when the grid has no rows to display
(`loading === false` AND the rendered row count is `0`, after all plugin
processing such as filtering / grouping / server-side).

- When **omitted**, a built-in message is rendered ("No data to display"
  or "No matching rows" when source rows existed but were filtered out).
- When set to a function, the function receives an EmptyContext
  and returns an `HTMLElement` or HTML string.
- When **explicitly `null`**, the empty overlay is suppressed entirely.

The empty overlay is mutually exclusive with the loading overlay; if
`loading === true`, the loading overlay always wins.

```typescript
// Show a backend error message via a closure over the consumer's state.
gridConfig.emptyRenderer = () =>
  error
    ? `Failed to load deals: ${error.message}`
    : 'No deals to display';
```

**See also:** [`EmptyOverlay`](/grid/api/core/types/emptyoverlay.md) to control where the overlay is mounted.

---

#### rowEditable

Row-level editability gate. Requires `EditingPlugin` to be loaded.

When provided, this function is called **before** the column-level
`editable` check. If it returns `false` for a given row, **no cell** in
that row can be edited regardless of the column configuration.

Omitting this property (or returning `true`) defers to per-column
`editable` settings.

Keep the callback fast — it is invoked on every editability check
(click, keyboard, grid-mode render, tab navigation).

```typescript
// Block editing for archived rows
gridConfig = {
  rowEditable: (row) => !row.archived,
  columns: [
    { field: 'name', editable: true },
    { field: 'price', editable: (row) => row.status === 'draft' },
  ],
};
```

---

#### filterable

Grid-wide filtering toggle. Requires `FilteringPlugin` to be loaded.

When `false`, disables filtering for all columns regardless of their individual `filterable` setting.
When `true` (default), columns with `filterable: true` (or not explicitly set to false) can be filtered.

This affects:
- Filter button visibility in headers
- Filter panel accessibility
- Filter keyboard shortcuts

**Default:** `true`

```typescript
// Disable all filtering at runtime
grid.gridConfig = { ...grid.gridConfig, filterable: false };

// Re-enable filtering
grid.gridConfig = { ...grid.gridConfig, filterable: true };
```

---

#### columnGroups

```ts
columnGroups: [
  { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },
  { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },
]
```

---

#### selectable

Grid-wide selection toggle. Requires `SelectionPlugin` to be loaded.

When `false`, disables all selection interactions while keeping the plugin loaded.
When `true` (default), selection works according to the plugin's mode configuration.

This affects:
- Click/drag selection
- Keyboard selection (arrows, Shift+arrows, Ctrl+A)
- Checkbox column clicks (if enabled)

**Default:** `true`

```typescript
// Disable all selection at runtime
grid.gridConfig = { ...grid.gridConfig, selectable: false };

// Re-enable selection
grid.gridConfig = { ...grid.gridConfig, selectable: true };
```

---
