---
id: "packages/active-grid/api/components-grid"
title: "ActiveGrid"
description: "ActiveGrid: generated from the source of active-grid."
url: "/packages/active-grid/api/components-grid"
locale: "en"
revision: "g0mvjxwj5c-88bdbe1c:components-grid"
---

# ActiveGrid

Exported by `@workspace/active-grid`. Generated from the package source; part of [the index of every export](/packages/active-grid/api).

## `ActiveGrid`

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `columns` | `GridColumnDef<TData>[]` | yes | Column definitions array. |
| `gridId` | `string` | yes | Unique identifier. Used for persistence keys and URL sync. |
| `mode` | `"client" \| "server"` | yes |  |
| `autoSizeOnMount` | `boolean \| Partial<AutoSizeOnMountOptions>` | no | Auto-size columns when grid mounts and data is available. |
| `calendarLocale` | `Locale` | no | Optional `date-fns/locale` object for the date-filter calendar. When omitted, the calendar resolves its locale from `locale` through active-format, which accepts any BCP 47 tag Intl formats: active-grid carries no language list of its own. Pass this to override that resolution for one grid. It does not replace the grid's value presentation or its time zone.  The calendar's accessible labels come from Intl for the locale's `code`, unless the object carries its own `labels` (a `react-day-picker/locale` import does); those win. |
| `className` | `string` | no | Additional CSS class on the grid container. |
| `columnDefaults` | `GridColumnDefaults<TData, unknown>` | no | Default values applied to all columns. |
| `columnResizeMode` | `"onChange" \| "onEnd"` | no | When to update column size — during drag or after release. |
| `columnTypes` | `GridColumnTypes<TData>` | no | Extends or overrides the built-in column types per type name; the built-in types apply without it. |
| `commitOnBlur` | `boolean` | no | When `true`, committing the current edit on blur (focus leaving the cell). When `false` (default), editing is only committed via Enter/Tab keys. Useful for spreadsheet-style editing where clicking away should save. |
| `contextMenu` | `ContextMenuBuilder<TData>` | no | Custom context menu builder: `(row, context?) => items`, called for a source row when its menu opens; `context` is a `ContextMenuContext`. |
| `data` | `TData[]` | no |  |
| `defaultMinSize` | `number` | no | Default minimum column width in pixels. Applies to all columns that don't specify their own `minSize`. Overrides the global config `columns.minWidth` for this grid instance. Default: 120. |
| `description` | `string` | no | Grid description. |
| `dir` | `"ltr" \| "rtl"` | no | The grid's UI direction: the reading order of its chrome, controls and columns, and the direction every popup it opens carries.  Left out, the grid inherits the direction of the page around it (the nearest `dir` attribute, else the computed CSS direction) and follows a later change of that attribute. It is never inferred from `locale`: an Arabic presentation in an `ltr` page stays `ltr`. A host that wants locale-driven direction sets `dir` on its own container.  This is not the direction of a text run; see the column's `meta.textDirection`. |
| `editMode` | `EditMode` | no | How edits are committed. - `'cell'` (default) / `'row'` — commit through `onDataCommit`. - `'batch'` — buffer changes and commit through `onBatchCommit`.  **The mode and its callback are a pair.** Editing is fail-closed: without the matching callback the grid offers no editable cells and no BatchToolbar, and emits a dev-mode warning once per `gridId`. Supplying the other mode's callback does not substitute for it. |
| `editTrigger` | `"click" \| "doubleClick"` | no | How editing is triggered on editable cells. - `'doubleClick'` (default) — double-click to start editing. - `'click'` — single click to start editing (spreadsheet-style). |
| `enableCellSelection` | `boolean` | no | Enable cell/range selection. When enabled, users can click to select individual cells, drag to select rectangular ranges, and Shift+click to extend selections. Works alongside row selection.  Default: `false`. |
| `enableColumnPinning` | `boolean` | no | Allow columns to be pinned left/right. |
| `enableColumnReordering` | `boolean` | no | Allow columns to be reordered via drag-and-drop, and adds "Move left" / "Move right" to the column menu as the keyboard path (see `guides/keyboard.md`). |
| `enableColumnResizing` | `boolean` | no | Allow columns to be resized by dragging, and adds "Increase width" / "Decrease width" to the column menu as the keyboard path (see `guides/keyboard.md`). |
| `enableColumnVirtualization` | `boolean` | no | Enable column virtualization. When `true`, only visible columns (plus pinned columns) are rendered in the DOM. Significantly reduces DOM node count for grids with many columns (50+).  Opt-in. Requires a fixed `height` (not `'auto'`) since the scroll container is needed for horizontal scroll position tracking.  Default: `false`. |
| `enableExpanding` | `boolean` | no | Allow rows to be expanded to show detail panels. |
| `enableFilterBuilder` | `boolean` | no | Enable the filter builder panel. When enabled, a toolbar button opens a collapsible panel above the grid where users can compose complex AND/OR filter expressions with nested groups.  The filter builder works alongside column filters — rows must pass both. Default: `false`.  **Client mode only.** The composed expression is evaluated into the same client-side predicate as {@link GridBaseProps.externalFilter} and is not serialised onto the wire, so in **server mode the toolbar button and the panel are not rendered** and setting the prop dev-warns once. Withholding the UI is deliberate: a rendered builder would let a user compose an expression that the grid then silently discards. Use the per-column filter row in server mode — that one does reach the server. Roadmap item `F-12` covers wiring the expression into the server query. |
| `enableGroupFooter` | `boolean` | no | Show a subtotal footer row below each expanded group. Each column that has `meta.aggregationFn` defined will display the aggregated value for the group's leaf rows. Requires `enableGrouping` to be active.  Default: `false`. |
| `enableGrouping` | `boolean` | no | Enable row grouping. Columns with `enableGrouping: true` (default) can be grouped via header menu. Shows `GroupingToolbar` with removable chips when grouping is active, directly above the `role="grid"` element as the top of the grid's card. |
| `enableHiding` | `boolean` | no | Enable column hiding. When false, removes all column visibility UI and prevents columns from being hidden. Default: true. |
| `enableKeyboardNavigation` | `boolean` | no | Cell-level keyboard navigation: arrow keys move focus between cells, Enter/Space activates the focused cell, Enter starts an edit on an editable cell.  **Defaults to `true`.** The grid declares `role="grid"`, and a grid is keyboard-navigable by definition — shipping the role without the navigation is an incorrect declaration (WCAG 2.1.1, level A).  Set to `false` to opt out; the cell plane is then unreachable by keyboard. |
| `enableMultiRowSelection` | `boolean` | no | Enable multi-row selection. When `false`, only one row can be selected at a time (single-select mode). The header "select all" checkbox is hidden in single-select mode. Default: `true`. |
| `enablePersistence` | `boolean` | no | Persist grid state (sorting, filters, sizing) to localStorage. Restored state is structurally validated — see State persistence. |
| `enableResponsiveHiding` | `boolean` | no | Enable responsive column hiding. When `true`, columns with a `meta.responsivePriority` value are automatically hidden/shown based on the grid container width. Higher priority numbers are hidden first.  Requires `enableHiding` to not be `false`. Default: `false`. |
| `enableRowPinning` | `boolean` | no | Allow rows to be pinned to top/bottom. |
| `enableRowSelection` | `boolean \| ((row: Row<TData>) => boolean)` | no | Whether rows may be selected, or a predicate deciding it per row.  The predicate receives the grid's ROW, not the record: it is handed straight to the engine's own option, which calls it with the row. Read the record through `row.original`; a predicate written against record fields reads `undefined` and refuses every row. |
| `enableServerGrouping` | `boolean` | no | Enable server-side grouping. When enabled with `mode="server"` and `enableGrouping`, group data is fetched from the server lazily per group expand instead of grouping client-side.  The `fetchFn` will receive `groupBy`, `groupKeys`, and `groupAggregations` parameters. The server must return `isGrouped` and `groups` in the response for group-level requests.  Default: `false`. |
| `enableUndoRedo` | `boolean` | no | Enable undo/redo for cell edits via Ctrl+Z / Ctrl+Y (Cmd on Mac). Requires `onDataCommit` to be set. Each successful commit is recorded in the history stack. Undo/redo replay commits through `onDataCommit` so the data source stays synchronised.  Default: `true` when `onDataCommit` is set, `false` otherwise. |
| `enableUrlSync` | `boolean` | no | Sync grid state to URL query parameters. The grid owns exactly five keys and leaves every other query parameter alone — see URL state. |
| `export` | `boolean \| GridExportOptions` | no | Export configuration. `true` enables all formats. `onExportComplete(format, scope)` / `onExportError(format, error, scope)` report the outcome of a menu export. `fonts` (`GridExportFonts \| 'inherit'`) sets this grid's PDF export fonts over the `GridDefaults` set. |
| `externalFilter` | `((row: TData) => boolean)` | no | External filter predicate applied alongside built-in column filters.  **Client mode only.** Rows that fail the predicate are excluded from the filtered row model before the engine sees them, so pagination, facets and sorting all operate on the filtered subset.  In **server mode the predicate is never called** — the server has already decided which rows to send, and filtering the loaded page in the browser would make the row counts disagree with the data. Setting it on a server-mode grid dev-warns once and is otherwise ignored; express the condition in the server query instead (`fetchFn` params, or a resource filter).  Note the consequence when the predicate is used to *hide* rows: in server mode the user sees more than the consumer intended. This is a visibility gap, not a data leak — permission and tenant scoping are enforced server-side by the resource layer, never by this predicate.  The predicate is called for every row on every render cycle, so keep the implementation fast (avoid closures over large data structures). |
| `fetchFn` | `ServerFetchFunction<TData>` | no |  |
| `getRowCanExpand` | `((row: Row<TData>) => boolean)` | no | Predicate to control which rows can be expanded. |
| `getRowId` | `((row: TData) => string) \| ((row: TData) => string)` | no | Custom row-ID resolver. Falls back to `row.id`. Required at the type level when `TData` has a closed key set without an `id` field. Must return a non-empty string; any other answer is refused with an error naming the record's position. Rows the grid generates itself (server-grouping headers, load-more rows) are named by the grid and never passed to it. |
| `gridRef` | `MutableRefObject<GridImperativeHandle \| null>` | no | Ref that receives imperative grid methods: `refetch()` to reload server data, and `resetSelection()` to clear all selected rows. |
| `headerMenu` | `GridHeaderMenuConfig` | no | Per-column header menu configuration. |
| `height` | `number \| "auto"` | no | Controls the grid height and virtualization behavior. - `number` — fixed height in pixels. Grid scrolls internally. Virtualization is enabled when row count exceeds the threshold. - `'auto'` — grid grows with content. No scroll container. Virtualization is disabled. - `undefined` — defaults to 400px with a dev-mode warning recommending an explicit height. |
| `initialFilterExpression` | `FilterGroup` | no | Initial filter expression for the filter builder. Applied once on mount. Client mode only, like the builder itself. |
| `initialGrouping` | `GroupingState` | no | Initial grouping state applied once on mount. Subsequent changes to this prop are ignored — use `grid.setGrouping()` for runtime updates. |
| `initialPageSize` | `number` | no | The page size a grid OPENS with when it has no pagination state of its own yet. Default 10.  Precedence, from strong to weak:  1. an explicit valid pagination state in the URL (`enableUrlSync`); 2. an explicit valid persisted state (`enablePersistence`) -- 1 and 2 ordered among themselves by `statePriority`; 3. this prop; 4. `GridConfig.pagination.pageSize` from the nearest provider; 5. the package default 10.  The prop therefore never overwrites what the user chose or shared himself, and it sets the state of the FIRST render: there is no visible jump from 10 to the configured size. A later render with a different value does not touch a running grid -- the starting state is determined once.  A present but invalid value (zero, negative, fractional, `NaN`) is refused, not clamped or rounded: it counts as absent, so the provider config or else the package default decides, and the refusal is reported in dev. See `state/initial-pagination.ts`. |
| `isRowPinnable` | `((row: TData) => boolean)` | no | Predicate to control which rows can be pinned. |
| `loadingDisplay` | `LoadingDisplayOptions \| LoadingIndicator` | no | What the grid shows while a fetch is in flight.  **The default is `{firstLoad: 'none', refetch: 'progress'}`** — on a first load the grid shows nothing at all. That is deliberate: only the consumer knows whether their data arrives in 50 ms or in 3 seconds, and an indicator that appears and vanishes inside 80 ms is more disturbing than an empty area. A library cannot make that call for you.  A refetch is the one exception, and it is not a matter of taste: rows are already on screen and they are now stale. Without a cue the reader is looking at old data without knowing it, so the 2-pixel bar stays on by default. Set `refetch: 'none'` if you disagree.  Independently of this prop, the grid never *claims* a result count it does not have: the pagination footer stays silent until a fetch has answered. That is correctness, not appearance, and it is not configurable.  Indicators: - `'none'` — nothing. - `'progress'` — 2-pixel indeterminate bar along the top edge. Covers nothing; rows stay visible and interactive. - `'skeleton'` — placeholder rows matching the column widths, over an opaque background. Row count follows `pageSize`, so the table does not jump when data lands. Degrades to `'none'` when column widths are not resolved yet. - `'spinner'` — centered spinner over an opaque background. - `'text'` — a single non-blocking line at the top edge ("Loading…" / "Refreshing data…"). Covers nothing, so it is safe in both states.  A `ReactNode` may be given instead of an indicator name. It is rendered as-is inside the grid box, which is the positioning context.  The short form is symmetric: `loadingDisplay="skeleton"` is exactly `{firstLoad: 'skeleton', refetch: 'skeleton'}`. |
| `locale` | `ValuePresentation` | no | Presentation input for every human-facing value the grid renders: body cells, footers, group aggregates, date filters, presentation export and clipboard copy. Accepts a locale code (`'nl'`, `'en'`, …) — notation follows the language — or a presentation-context object (`{languageLocale, dateShape?, dateFormat?, timeFormat?, numberFormat?, currencyDisplay?}`) carrying language and notation independently. `dateShape` is what a plain `date`/`dateTime` column inherits when it declares no `meta.datePreset`. The grid forwards the value untouched to the central active-format formatters and never resolves tenant or application settings itself.  This instance's override of the nearest `<GridDefaults locale>`, replacing it whole. There is no default: a human formatter never invents a language (active-format D14), so with neither the prop nor an inherited presentation the grid throws a configuration error at render.  When passing an object, keep its identity stable across renders (memoize in the consumer) — it participates in internal dependency arrays. |
| `maxPinnedRows` | `number` | no | Maximum number of pinned rows. |
| `maxUndoDepth` | `number` | no | Maximum number of entries in the undo history stack. Oldest entries are discarded when the limit is exceeded. Default: 50. |
| `meta` | `Partial<GridMeta<TData>>` | no | Custom metadata passed to the grid instance. Used internally for editing-state, pinning-state, copy-callbacks (`onCopySuccess`, `onCopyError`), aggregations, facets, and more. |
| `minGridWidth` | `number` | no | Minimum total grid width in pixels. Flex columns are distributed over at least this width (even when the viewport is narrower). The container never shrinks below this value, but always grows to fit all columns — `minGridWidth` is a floor, not a cap. |
| `onBatchCommit` | `((payload: BatchCommitPayload<TData>) => Promise<void \| TData[]>)` | no | Callback for batch editing mode. Called when the user clicks "Save All". Receives all dirty changes grouped by row. Each entry in `changes` has the same shape as `EditingCommitPayload`. Return an array of updated rows to patch the local cache, or void to rely on a full refetch.  Only used when `editMode="batch"`, and **required** there — batch mode has no other commit path, so without this callback editing is disabled entirely (fail-closed) with a dev-mode warning. Conversely this callback is unreachable outside batch mode: `editMode="cell"` / `"row"` commit through `onDataCommit`. |
| `onCellActivate` | `((row: TData, columnId: string) => void)` | no | Fired when Enter or Space activates a focused cell.  Not fired for editable cells: there Enter starts the edit instead, so a single keypress never both activates and edits. |
| `onCellClick` | `((row: TData, columnId: string, value: unknown) => void)` | no | Fires on cell click with row data, column ID, and value. Both `onCellClick` and `onRowClick` fire on the same click (cell first via event bubbling). |
| `onCellSelectionChange` | `((ranges: CellRange[]) => void)` | no | Callback fired when the cell selection changes. Receives the array of currently selected ranges. |
| `onDataCommit` | `((payload: EditingCommitPayload<TData>) => Promise<void \| TData>)` | no | Callback when edited data is committed. **Required** for `editMode="cell"` / `"row"` — without it those modes offer no editing. |
| `onFilterChange` | `((filters: ColumnFiltersState) => void)` | no | Callback fired when the column filters change. Receives the new column filters state array. |
| `onFilterExpressionChange` | `((expression: FilterExpression \| null) => void)` | no | Callback fired when the filter expression changes. Receives the full expression tree or null when cleared.  Never fires in server mode — no expression can be composed there. |
| `onGroupingChange` | `((grouping: GroupingState) => void)` | no | Callback fired when grouping state changes. |
| `onRowClick` | `((row: TData) => void)` | no | The row callbacks and the row menu are about a record: a row grouping generated has none, so on it they are not called and no row menu opens. |
| `onRowDoubleClick` | `((row: TData) => void)` | no | Callback when a row is double-clicked. |
| `onSelectionChange` | `((selectedRows: TData[]) => void)` | no | Callback fired when the set of selected rows changes. Receives the currently selected row data objects. |
| `onSelectionModelChange` | `((model: SelectionModel) => void)` | no | Callback fired when the selection model changes. Unlike `onSelectionChange` (which provides selected row data), this provides the structural selection model — useful for server-side grids where the consumer needs to know whether the selection is inclusive (specific IDs) or exclusive (all except specific IDs). |
| `onSortingChange` | `((sorting: SortingState) => void)` | no | Callback fired when the sorting state changes. Receives the new sorting state array. |
| `pagination` | `GridPaginationOptions` | no | Pagination configuration. |
| `postSortRows` | `((rows: TData[]) => TData[])` | no | Post-sort processing callback. Called after sorting is applied but before pagination. Use this to reorder rows after sorting — e.g. keep pinned/ favorite rows at the top, or ensure a "total" row stays at the bottom.  Client mode only. The callback receives the sorted row data array and must return a new array with the desired order. |
| `renderDetailPanel` | `((row: Row<TData>) => ReactNode)` | no | Render function for the expanded detail panel. |
| `responsiveBreakpoints` | `Record<number, number>` | no | Breakpoints for responsive column hiding. Maps minimum container widths (px) to the maximum `responsivePriority` value that is visible at that width. Columns with a priority > the threshold are hidden.  Default: `{ 0: 1, 640: 2, 1024: 3, 1280: Infinity }`  With the default: at 0–639px only priority ≤1 columns are shown, at 640–1023px priority ≤2, at 1024–1279px priority ≤3, at ≥1280px all. |
| `restoreFocusRef` | `MutableRefObject<(() => boolean) \| null>` | no | Ref that receives the restoreFocus callback. Call it after closing a modal/dialog to return focus to the last focused cell. |
| `rowMenu` | `GridRowMenuConfig` | no | Row context menu configuration. |
| `searchPlaceholder` | `string` | no | Placeholder text for the global search input. |
| `selectionActions` | `readonly GridSelectionAction[]` | no | Declarative bulk actions for the current selection.  When this is a non-empty array and rows are selected, the grid renders its selection action bar — `[N selected] [Actions ▾]` — above the body, and suppresses the footer's selection counter so the same number is never stated twice. The shape does not change with the number of actions: a single action lives in the same menu as five.  Requires `enableRowSelection`. Inert without it. |
| `serverCapabilities` | `GridServerCapabilities` | no | Server-declared UI capabilities for a resource-backed grid (server-mode). When provided, this is the single source of the grid's sort/filter/group affordances: the setup synthesizes an internal `column.server` contract from it, so the enable-flag resolution and hydration pruning run unchanged and columns need not carry a per-column `server` marker. Supplied by the page from the resource's `deriveCapabilities` map (see `active-resources`). |
| `serverGroupBy` | `GroupByColumn[]` | no | Columns to group by in server-side grouping mode. Order determines the nesting hierarchy. |
| `serverOptions` | `ServerOptions` | no | Server mode options: page caching, prefetch, and debounce. Only applies when `mode="server"`. Works out-of-the-box with defaults. |
| `serverQueryKey` | `string` | no | Opaque identity of the EXTERNAL query — the part of the question the grid does not own. Server-mode only; ignored in client mode.  **The problem it solves.** A grid owns its page, sort, column filters and search, and it already treats a change in any of those as a new result set. But a page can hold a second set of controls OUTSIDE the grid — a search box in a page toolbar, a folder picker, a kind filter carried in the URL — that narrows the same query server-side. The grid cannot see those. Without a signal it keeps serving the cached blocks of the previous scope, and it leaves the reader on page 5 of a result set that now has one page: an empty body with no explanation.  **What a change does.** The grid treats the result set as new: it invalidates its block cache, refetches, resets `pageIndex` to 0 and clears the row selection. That is exactly what it already does for its own filters — this prop only widens WHAT counts as a new query, it adds no second fetch path.  **What it is not.** Not a value the grid sends anywhere, not a cache key you can read back, and not authorization. The grid never parses it; any string that changes when — and only when — the external question changes is correct. Build it from the values themselves (`` `${q}\|${kind}` ``), so returning to a previous scope is genuinely the same key.  Leave it undefined when the grid owns the whole question. The dataset key is then derived from the grid's own query state only. |
| `settings` | `GridSettings<TData>` | no | Visual settings (row height, striping, footer styling). |
| `showRowNumbers` | `boolean` | no | Show row numbers as the first column. Default: false. |
| `statePriority` | `GridStatePriority` | no | Conflict-resolution strategy between the URL (`enableUrlSync`) and localStorage (`enablePersistence`) for the four *contested* state slices: `sorting`, `columnFilters`, `globalFilter`, `pagination`. Resolution is per-slice, so a URL that carries only sorting still lets localStorage supply the filters.  The column-shape slices (visibility, order, sizing, pinning, rowPinning, expanded) live only in localStorage and are outside this strategy — they always follow localStorage while `enablePersistence` is on.  - `'url-first'` (default) — URL wins when present, else localStorage, else the feature default. URL is explicit intent (bookmark, shared link); localStorage is implicit memory. - `'localstorage-first'` — localStorage wins when present, else URL. - `'url-only'` — contested slices come from the URL only. - `'localstorage-only'` — contested slices come from localStorage only.  Only has an effect when both sources are enabled; with a single source it is a no-op. Defaults to `'url-first'`. |
| `t` | `GridTranslateFunction` | no | The translator for every grid text: this instance's override of the nearest `<GridDefaults t>`. It is AUTHORITATIVE: a key it does not carry renders as that raw key and is never replaced by English. With neither the prop nor an inherited translator the grid throws a configuration error; `englishGridTranslator` is the package's English, passed on purpose. |
| `title` | `string` | no | Grid title (displayed in toolbar). |
| `toolbar` | `GridToolbarConfig<TData>` | no | Toolbar configuration (search, actions). |
| `virtualization` | `boolean` | no | Controls row virtualization. - `true` — virtualization always on (requires `height={number}`) - `false` — virtualization always off, all rows rendered in DOM - `undefined` (default) — auto-detect based on height and row count exceeding threshold  When `height="auto"`, virtualization is always off regardless of this prop. |
| `virtualizationThreshold` | `number` | no | Override the row count threshold above which virtualization is enabled (when `height` is a number). Defaults to the global config `virtualizationThreshold` (1000). |
