# Benchmarks
These benchmarks compare gp-grid against other popular data grid libraries on a
single **1,000,000-row** dataset, using an identical set of columns and the same
seeded data for every grid. Each metric below is the median across repeated
iterations. The exact machine, runtime and library versions used for this run
are listed with the results so the numbers are reproducible.
## Methodology
* **Same data, same columns.** Every grid renders the same deterministically
generated rows and columns; only the grid under test changes.
* **Windowed virtualization.** All grids keep just the visible row window in the
DOM. gp-grid caps its scroll container at 10,000,000px because browsers
mis-render taller elements; above \~312,000 rows (at a 32px row height) the
natural content height exceeds that cap, so gp-grid compresses the scroll
space (a scroll ratio below 1) and maps the scrollbar position back to logical
row coordinates. At 1,000,000 rows this compression is active. Grids that
instead render a full-height spacer (row count × row height) rely on the
browser's own maximum element-height limits.
* **Identical wheel input.** The measured scroll sends the same synthetic
mouse-wheel events to every grid. Each grid's scroll and virtualization model
translates that input into a different travelled distance (custom scrollbars
and dampened virtual-scroll wheel handling rescale the deltas), so instead of
forcing an equal distance through grid-specific APIs, the tables report
**Rows Traversed** — how many rows each grid actually covered under the same
input.
* **Mode column.** Each table lists how a grid is wired up. `native-grid` is a
shipped grid component; `headless-table-virtualizer` supplies only the
data/virtualization model while the benchmark authors the DOM;
`app-side-virtual-data` feeds a virtual data source from the benchmark. The
Notes section below the results spells out the per-grid details.
## Metrics Explained
| Metric | Better | Description |
| ------------------ | ------ | ----------------------------------------------------------------------------------------------------------- |
| **Avg FPS** | Higher | Average frames per second during scroll (frames ÷ elapsed time) |
| **P05 FPS** | Higher | 5th-percentile FPS — the slow end of the distribution |
| **P95 Frame Time** | Lower | 95th-percentile frame duration; lower means fewer long frames |
| **Frame Drops** | Lower | Number of frames that took longer than 25ms |
| **Rows Traversed** | — | Rows covered during the measured scroll (logical scroll delta ÷ row height) under the identical wheel input |
| **Scroll Delta** | — | Logical (content) pixels travelled during the measured scroll |
| **Scroll px/s** | — | Logical pixels travelled per second during the measured scroll |
| **FCP** | Lower | Time to first contentful paint |
| **Full Render** | Lower | Time until the grid is fully rendered and interactive |
| **LCP** | Lower | Largest Contentful Paint |
| **TBT** | Lower | Total Blocking Time |
| **After Load** | Lower | Heap size after the data is loaded |
| **Peak** | Lower | Maximum heap size observed during the run |
| **Growth / 1K** | Lower | Heap increase per 1,000 rows |
| **Retained** | Lower | Heap not released after clearing the data |
| **Minified** | Lower | Minified production JS + CSS of the grid's packages, bundled with Vite (React/React DOM excluded) |
| **Gzip** | Lower | The same bundle after gzip compression |
## Run Details
* **Run:** 2026-07-16T10-29-40-792Z
* **Date:** 2026-07-16T10:29:40.796Z
* **Machine:** Intel(R) Core(TM) Ultra 7 258V
* **Environment:** win32 10.0.26200, 8 logical CPUs, 32244 MB RAM
* **Runtime:** Node v24.14.0, Chrome 150.0.7871.116
* **Config:** 10 iterations, 1 Playwright worker, retries 0, overscan 10 rows, 32px row height, 1280x720, headless true
* **Scope:** DOM-rendering grids. Canvas-rendering grids are excluded: their painting model is not directly comparable.
## Library Versions
| Grid | Package | Version |
| -------------- | --------------------------- | ------------- |
| gp-grid | `@gp-grid/react` | 0.19.0 |
| ag-grid | `ag-grid-community` | 35.3.1 |
| ag-grid | `ag-grid-react` | 35.3.1 |
| tanstack-table | `@tanstack/react-table` | 9.0.0-beta.16 |
| tanstack-table | `@tanstack/react-virtual` | 3.14.3 |
| handsontable | `handsontable` | 17.1.0 |
| smart-grid | `smart-webcomponents-react` | 26.0.0 |
## Package Bundle Sizes
Production ESM JavaScript and CSS bundles of the exact grid-library imports used by each benchmark, minified by Vite and measured with gzip. React and React DOM peer dependencies are excluded; bundled transitive dependencies and imported CSS are included, while other emitted assets are excluded. Every package is sized at the published npm version shown in its Packages cell.
| Grid | Packages | Minified | Gzip |
| -------------- | ----------------------------------------------------------------------- | --------- | -------- |
| gp-grid | `@gp-grid/react@0.19.0` | 170.7 kB | 41.5 kB |
| ag-grid | `ag-grid-community@35.3.1`, `ag-grid-react@35.3.1` | 1098.7 kB | 306.7 kB |
| tanstack-table | `@tanstack/react-table@9.0.0-beta.16`, `@tanstack/react-virtual@3.14.3` | 67.1 kB | 19.2 kB |
| handsontable | `handsontable@17.1.0` | 1521.2 kB | 350.1 kB |
| smart-grid | `smart-webcomponents-react@26.0.0` | 4082.6 kB | 779.7 kB |
## Scroll Performance
Every grid receives the identical mouse-wheel input. Each grid's scroll and virtualization model translates that input into a different distance (custom scrollbars and dampened virtual-scroll wheel handling rescale the deltas), so the Rows Traversed column shows how many rows each grid actually covered during the measured pass.
| Grid | Mode | Rows | Avg FPS | P05 FPS | P95 Frame Time | Frame Drops | Rows Traversed | Scroll Delta | Scroll px/s |
| --------------------------------------------------------------------- | -------------------------- | ---- | ------- | ------------------ | -------------- | ----------- | -------------- | ------------ | ----------- |
| [gp-grid](https://www.gp-grid.io) | native-grid | 1M | 56.2 | 54.3 | 18.4ms | 0 | 501 | 16026px | 934 |
| [AG Grid](https://www.ag-grid.com/react-data-grid/) | native-grid | 1M | 41.85 | 44.349999999999994 | 27.15ms | 49 | 1563 | 50000px | 2184.5 |
| [Handsontable](https://handsontable.com/) | native-grid | 1M | 26.55 | 4 | 248.95ms | 69.5 | 1565 | 50080px | 1334 |
| [Smart.Grid](https://www.htmlelements.com/react/demos/grid/overview/) | app-side-virtual-data | 1M | 56.6 | 54.9 | 18.2ms | 2 | 159 | 5087px | 290.5 |
| [TanStack Table](https://tanstack.com/table/latest) | headless-table-virtualizer | 1M | 53.9 | 54.3 | 18.4ms | 2 | 1563 | 50000px | 4088.5 |
## Initial Render
| Grid | Mode | Rows | FCP | Full Render | LCP | TBT |
| --------------------------------------------------------------------- | -------------------------- | ---- | ------ | ----------- | ------ | -------- |
| [gp-grid](https://www.gp-grid.io) | native-grid | 1M | 1334ms | 1493.5ms | 1474ms | 1297ms |
| [AG Grid](https://www.ag-grid.com/react-data-grid/) | native-grid | 1M | 1348ms | 2530ms | 2430ms | 2153.5ms |
| [Handsontable](https://handsontable.com/) | native-grid | 1M | 388ms | 10202ms | 950ms | 9708.5ms |
| [Smart.Grid](https://www.htmlelements.com/react/demos/grid/overview/) | app-side-virtual-data | 1M | 172ms | 1359ms | 0ms | 921.5ms |
| [TanStack Table](https://tanstack.com/table/latest) | headless-table-virtualizer | 1M | 600ms | 2171.5ms | 1736ms | 1875.5ms |
## Sort/Filter Performance
| Grid | Mode | Rows | Sort Asc | Sort Desc | Multi Sort | Text Filter | Number Filter |
| --------------------------------------------------------------------- | -------------------------- | ---- | --------- | --------- | ---------- | ----------- | ------------- |
| [gp-grid](https://www.gp-grid.io) | native-grid | 1M | 652ms | 571.5ms | 1219.5ms | 188ms | 129.5ms |
| [AG Grid](https://www.ag-grid.com/react-data-grid/) | native-grid | 1M | 2104ms | 1620.5ms | 3044.5ms | 327ms | 281.5ms |
| [Handsontable](https://handsontable.com/) | native-grid | 1M | 33801.5ms | 33571ms | 45970.5ms | 2216.5ms | 1287ms |
| [Smart.Grid](https://www.htmlelements.com/react/demos/grid/overview/) | app-side-virtual-data | 1M | 1018ms | 523ms | 976.5ms | 191ms | 175.5ms |
| [TanStack Table](https://tanstack.com/table/latest) | headless-table-virtualizer | 1M | 1193ms | 1073.5ms | 2530.5ms | 224ms | 243.5ms |
## Memory Usage
| Grid | Mode | Rows | After Load | Peak | After Scroll | Growth / 1K | Retained |
| --------------------------------------------------------------------- | -------------------------- | ---- | ---------- | -------- | ------------ | ----------- | -------- |
| [gp-grid](https://www.gp-grid.io) | native-grid | 1M | 254.73MB | 267.82MB | 255.32MB | 0.25MB | 0.81MB |
| [AG Grid](https://www.ag-grid.com/react-data-grid/) | native-grid | 1M | 461.94MB | 610.68MB | 463.36MB | 0.46MB | 1.97MB |
| [Handsontable](https://handsontable.com/) | native-grid | 1M | 941.42MB | 945.48MB | 942.32MB | 0.94MB | 1.31MB |
| [Smart.Grid](https://www.htmlelements.com/react/demos/grid/overview/) | app-side-virtual-data | 1M | 244.89MB | 247.82MB | 245.36MB | 0.24MB | 239.94MB |
| [TanStack Table](https://tanstack.com/table/latest) | headless-table-virtualizer | 1M | 457.84MB | 481.44MB | 461.75MB | 0.46MB | 445.34MB |
## Notes
* **gp-grid:** Above \~312,000 rows gp-grid caps its DOM scroll container at 10,000,000px, compresses the scroll space, and dampens wheel deltas before applying them, so the shared wheel input traverses fewer rows than on the natively-scrolling grids (see Rows Traversed). Below that threshold it scrolls natively via the wheel like the other grids. Its scroll delta is reported in logical (content) pixels.
* **Smart.Grid:** Uses Smart.Grid virtualDataSource; sort/filter processing is performed by the benchmark adapter over the full in-memory dataset, then Smart.Grid renders the requested virtual window. Smart.Grid exposes no configurable row overscan, so the shared overscan setting does not apply to it. Its custom scrollbar rescales mouse-wheel input to fixed line steps rather than the event's delta, so under the shared wheel input it traverses a different number of rows than the natively-scrolling grids (see Rows Traversed).
* **TanStack Table:** TanStack Table is headless: it supplies the sort/filter/row models and, with @tanstack/react-virtual, the row virtualization, but the row and cell DOM is authored by this benchmark. Its render and scroll numbers therefore reflect the benchmark's own markup, not a shipped grid component.
## Reproduce
Clone [gp-grid](https://github.com/GioPat/gp-grid), then from the `benchmarks/` directory:
```bash
pnpm install
pnpm bench
```
Optionally override the defaults with environment variables before `pnpm bench`:
* `BENCH_ROW_COUNTS` — comma-separated row counts (default `1000000`)
* `BENCH_ITERATIONS` — iterations per grid (default `5`)
* `BENCH_RUN_ID` — a label for the run's results directory
# Introduction
A high-performance TypeScript data grid built on three core principles:
1. **Slot-Based Virtual Scrolling** - DOM elements are recycled rather than created/destroyed, maintaining consistent performance regardless of dataset size.
2. **Instruction-Based Architecture** - The core emits declarative instructions that framework adapters interpret, keeping rendering logic framework-agnostic.
3. **DataSource Abstraction** - Clean separation between data operations and UI, supporting both client-side and server-side data handling.
## Features
* Virtual scrolling with slot recycling
* Column dragging and reordering
* Row dragging and reordering
* Column resizing with min/max constraints
* Cell selection with range and multi-select
* Multi-column sorting
* Column filtering with debounced input
* Cell editing with fill handle
* Keyboard navigation
* Custom cell/edit/header renderers
* Dark mode support
* Full TypeScript support
* Zero external dependencies
## Packages
| Package | Description |
| -------------------------------------------------------------------- | ----------------------------- |
| [`@gp-grid/core`](https://www.npmjs.com/package/@gp-grid/core) | Framework-agnostic core logic |
| [`@gp-grid/react`](https://www.npmjs.com/package/@gp-grid/react) | Official React bindings |
| [`@gp-grid/vue`](https://www.npmjs.com/package/@gp-grid/vue) | Official Vue 3 bindings |
| [`@gp-grid/angular`](https://www.npmjs.com/package/@gp-grid/angular) | Official Angular bindings |
# Onboard your Agent 🧠
gp-grid ships with an official **agent skill** — a structured set of instructions, references, and worked examples that teaches a coding agent how to integrate the library correctly. Install it once and your editor's agent stops guessing at imports, renderer signatures, and data-source shapes.
## What the skill does for you
Once installed, the skill activates whenever you ask your agent to work with gp-grid. It triggers on `@gp-grid/*` imports, identifiers like `useGridData`, `createServerDataSource`, or `GpGridComponent`, and natural-language requests such as "wire up a gp-grid in this Vue app". It gives the agent:
* The correct **per-framework** imports, component name, and renderer signature for React, Vue 3, Angular, or vanilla `@gp-grid/core`.
* The `ColumnDefinition` shape, including the right `cellDataType` for every value type — `Date` vs ISO string is the most common source of subtle bugs.
* The data-source decision tree: when to pass `rowData` directly, when to use `createClientDataSource`, when to reach for `useGridData` (React) / `createGridData` (Vue) / `provideGridData` (Angular), and when to go server-side with `createServerDataSource`.
* The current `DataSourceRequest` shape — `range: { startRow, endRow }` (not `pagination`) — and a reminder that `endRow` is exclusive.
* Common pitfalls — missing container height, forgotten CSS import, mixing framework adapters — flagged before the agent writes code instead of after.
## Install via skills.sh
The fastest path is the [skills.sh](https://skills.sh) installer, which copies the skill into your repository's agent configuration:
npm
pnpm
```bash
npx skills add https://github.com/GioPat/gp-grid
```
```bash
pnpx skills add https://github.com/GioPat/gp-grid
```
Run it from the root of the project where you want gp-grid integrated. The installer detects which agents you have configured locally and writes the skill into each of them.
`skills.sh` is the central registry for agent skills — see [skills.sh](https://skills.sh) for the full catalog and how the install command picks targets.
## Multi-agent support
The same skill is installed for every supported coding agent — pick whichever fits your workflow
The installer writes the skill to each agent's expected configuration directory. No further setup is required — open your editor, ask the agent to use gp-grid, and the skill is consulted automatically.
## What's inside
The skill is a single `SKILL.md` plus per-framework reference files (`references/react.md`, `references/vue.md`, `references/angular.md`, `references/core.md`). The agent reads the framework reference that matches your project before generating any code, which keeps the integration faithful to the wrapper you're using.
Browse the source on GitHub: [gp-grid/.claude/skills/gp-grid-integration](https://github.com/GioPat/gp-grid/tree/master/.claude/skills/gp-grid-integration).
## Updating the skill
Re-run the installer to pull the latest version:
npm
pnpm
```bash
npx skills add GioPat/gp-grid
```
```bash
npx skills add GioPat/gp-grid
```
The skill is versioned alongside the library, so re-installing after a gp-grid release keeps your agent in sync with API changes.
# Why GP-Grid
GP-Grid is designed from the ground up for performance, simplicity, and developer experience with no feature gating.
## Performance First
### Virtual Scrolling That Actually Works
Most grids claim virtual scrolling, but struggle with millions of rows. GP-Grid uses a unique **slot-based architecture** that maintains consistent 60fps performance regardless of dataset size.
See our [detailed benchmarks](/docs/benchmarks) comparing GP-Grid against other popular grid libraries.
### How It Works
Instead of creating and destroying DOM nodes as you scroll, GP-Grid recycles a fixed pool of "slots". This means:
* **Constant memory usage** - No matter how many rows you have
* **No garbage collection pauses** - DOM nodes are reused, not discarded
* **Smooth scrolling** - No jank from element creation
## TypeScript Native
GP-Grid is written entirely in TypeScript with modern tooling (tsdown, rolldown, etc) and strict type checking. You get:
* Full type inference for your data
* Autocomplete for all props and methods
* Compile-time error checking
* No `@types` packages needed
```tsx
interface User {
id: number;
name: string;
email: string;
}
// Full type safety
const columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80 },
{ field: "name", cellDataType: "text", width: 200 },
{ field: "email", cellDataType: "text", width: 250 },
];
// TypeScript knows your data shape
columns={columns} rowData={users} rowHeight={36} />
```
## Framework Agnostic Core
The core logic is completely separated from UI rendering. This means:
* **Consistent behavior** across React and Vue
* **Easy to add new frameworks** - The adapter layer is thin
* **Battle-tested core** - One codebase, multiple implementations
## Zero Dependencies — Lightweight — Everything Included
GP-Grid has **0 external dependencies**. The entire library is self-contained — no transitive dependency chains, no supply-chain risk, no version conflicts. What you install is what you get.
It's also significantly smaller than alternatives, and every feature ships for free:
| Library | ReactJS Bundle Size CORE + WRAPPERS (minify + gzip) | Dependencies | Feature Gating | License |
| ------------ | --------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------- | ---------------- |
| **GP-Grid** | **31 kB** | **0** | **None — all features included** | Apache 2.0 |
| AG-Grid | 479 kB | 0 | Enterprise license required for advanced features (row grouping, pivoting, charts, etc.) | MIT / Commercial |
| Handsontable | 335 kB+ | Multiple | Commercial license required for most features | Commercial |
No bloat. No dependencies. No paywalled modules. No enterprise traps. Just what you need — all of it.
## Simple API
No complex configuration objects. No steep learning curve.
```tsx
// That's it. A working grid.
```
Compare this to competitors that require pages of configuration.
## Features When You Need Them
* **Column Dragging** - Reorder columns by dragging headers
* **Row Dragging** - Reorder rows with drag handle or entire row mode
* **Column Resizing** - Drag header edges with min/max constraints
* **Sorting** - Click headers, hold Shift for multi-sort
* **Filtering** - Built-in filter popup with operators
* **Selection** - Cell, range, and multi-select
* **Editing** - Inline editing with fill handle
* **Keyboard** - Full keyboard navigation
* **Dark mode** - One prop to toggle
## Open Source — No Enterprise Traps
GP-Grid is Apache 2.0 licensed and community driven. Every feature is included for free — no enterprise tiers, no feature gating, no paywalled modules. Column dragging, row reordering, filtering, editing — it's all included out of the box.
# Basic Usage (Angular)
This page is being written. The [React Basic Usage](/docs/react/basic-usage) and [Vue Basic Usage](/docs/vue/basic-usage) pages cover the same concepts — props, column definitions, and data types are identical across bindings.
Don't forget to import the stylesheet in your global `styles.css`:
```css
@import "@gp-grid/angular/dist/styles.css";
```
## Minimal Example
```ts
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-my-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class MyGridComponent {
columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 200, headerName: "Name" },
];
data = [
{ id: 1, name: "Giovanni Rossi" },
{ id: 2, name: "Luca Verdi" },
];
}
```
## Next Steps
* Explore the [Examples](/docs/angular/examples)
* See the [API Reference](/docs/angular/api)
# FAQ (Angular)
Angular-specific FAQs are being written. See the [React FAQ](/docs/react/faq) for framework-agnostic answers — most apply to the Angular binding as well.
# Getting Started (Angular)
gp-grid provides official Angular bindings through the `@gp-grid/angular` package.
Angular docs are being written. While this section fills in, the [React](/docs/react) and [Vue](/docs/vue) docs cover the same conceptual model — the API surface is identical across all three bindings.
## Quick Start
npm
pnpm
bun
yarn
```bash
npm install @gp-grid/angular
```
```bash
pnpm add @gp-grid/angular
```
```bash
bun add @gp-grid/angular
```
```bash
yarn add @gp-grid/angular
```
```ts title="my-grid.component.ts"
import { Component } from "@angular/core";
// [!code highlight]
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-my-grid",
standalone: true,
// [!code highlight]
imports: [GridComponent],
template: `
`,
})
export class MyGridComponent {
columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
];
data = [
{ id: 1, name: "Giovanni", email: "giovanni@example.com" },
{ id: 2, name: "Luca", email: "luca@example.com" },
];
}
```
## Next Steps
# Installation (Angular)
## Package Manager
Install [`@gp-grid/angular`](https://www.npmjs.com/package/@gp-grid/angular) using your preferred package manager:
npm
pnpm
bun
yarn
```bash
npm install @gp-grid/angular
```
```bash
pnpm add @gp-grid/angular
```
```bash
bun add @gp-grid/angular
```
```bash
yarn add @gp-grid/angular
```
## Requirements
* Angular 17 or higher (standalone components)
* TypeScript 5.0+ (recommended)
## Peer Dependencies
* `@angular/core` >= 17.0.0
* `@angular/common` >= 17.0.0
## TypeScript
gp-grid is written in TypeScript and includes built-in type definitions:
```ts
import {
GpGridComponent,
type ColumnDefinition,
type GridProps,
} from "@gp-grid/angular";
```
## Styles
Starting from `v0.11.0`, gp-grid no longer auto-injects its CSS to avoid [FUOC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content), this gives you the full flexibility of customizing gp-grid styles!. You must import the stylesheet once in your application. The recommended approach is in your global `styles.css`:
```css
@import "@gp-grid/angular/dist/styles.css";
```
Or in `angular.json` under the `styles` array:
```json
"styles": [
"src/styles.css",
"node_modules/@gp-grid/angular/dist/styles.css"
]
```
## Next Steps
* See [Basic Usage](/docs/angular/basic-usage) for your first grid
# Basic Usage (React)
This guide covers the fundamentals of using gp-grid in your React application.
Don't forget to import the stylesheet once at the root of your app:
```tsx
import "@gp-grid/react/dist/styles.css";
```
## Minimal Example
The simplest grid requires three things: columns, data, and a row height.
```tsx
import { Grid, type ColumnDefinition } from "@gp-grid/react";
const columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 200, headerName: "Name" },
];
const data = [
{ id: 1, name: "Giovanni Rossi" },
{ id: 2, name: "Luca Verdi" },
];
function MyGrid() {
return (
);
}
```
## Container Sizing
The grid fills its parent container. Always ensure the parent has explicit dimensions:
```tsx
// Using CSS
// Using Tailwind CSS
```
## Column Definitions
Each column requires at minimum a `field`, `cellDataType`, and `width`:
```tsx
const columns: ColumnDefinition[] = [
{
field: "id", // Property name in row data
cellDataType: "number", // Data type for formatting
width: 80, // Column width in pixels
headerName: "ID", // Optional display name
},
{
field: "email",
cellDataType: "text",
width: 250,
headerName: "Email Address",
editable: true, // Enable editing
sortable: true, // Enable sorting (default: true)
filterable: true, // Enable filtering (default: true)
},
];
```
### Cell Data Types
| Type | Description |
| ---------------- | ---------------------- |
| `text` | String values |
| `number` | Numeric values |
| `boolean` | True/false values |
| `date` | Date objects |
| `dateString` | Date as ISO string |
| `dateTime` | DateTime objects |
| `dateTimeString` | DateTime as ISO string |
| `object` | Complex objects |
## Dark Mode
Enable dark mode styling with the `darkMode` prop:
```tsx
```
For dynamic theme support with next-themes:
```tsx
"use client";
import { useTheme } from "next-themes";
import { Grid } from "@gp-grid/react";
function ThemedGrid() {
const { resolvedTheme } = useTheme();
return (
);
}
```
## Header Height
By default, the header height equals the row height. Override it with `headerHeight`:
```tsx
```
## Overscan
Control how many rows are rendered outside the visible viewport for smoother scrolling:
```tsx
```
## Next Steps
* Learn about [Sorting](/docs/react/examples/sorting) and [Filtering](/docs/react/examples/filtering)
* Explore [Cell Selection](/docs/react/examples/selection)
* See the complete [API Reference](/docs/react/api)
# FAQ (React)
Frequently asked questions about @gp-grid/react.
## General
### What browsers are supported?
gp-grid supports all modern browsers:
* Chrome/Edge 88+
* Firefox 78+
* Safari 14+
### Is gp-grid free to use?
Yes, gp-grid is open source and free to use in both personal and commercial projects.
### What's the maximum dataset size?
There's no practical limit. The virtual scrolling architecture means performance is consistent regardless of dataset size. We've tested with 2 million rows.
## Performance
### Why is my grid slow to render?
Common causes:
1. **Not using memoization**: Wrap `rowData` in `useMemo`:
```tsx
const rows = useMemo(() => data.map(transform), [data]);
```
2. **Heavy custom renderers**: Keep renderers simple, pre-compute values.
3. **Too many columns**: Consider horizontal virtualization for 50+ columns.
### How do I improve scroll performance?
* Increase `overscan` for smoother scrolling (at cost of more DOM nodes)
* Simplify custom renderers
* Ensure container has explicit dimensions
## Styling
### How do I enable dark mode?
Use the `darkMode` prop:
```tsx
```
For theme integration (e.g., next-themes), see the [Styling Guide](/docs/react/guides/styling).
### How do I customize the grid appearance?
Override CSS classes in your global styles. Common classes include `.gp-grid-header`, `.gp-grid-cell--active`, and `.gp-grid-row`. For complete styling options including Tailwind CSS and custom renderers, see the [Styling Guide](/docs/react/guides/styling).
## Data
### How do I update a single cell?
Use the `useGridData` hook — it's the React-idiomatic wrapper around the mutable data source:
```tsx
const { dataSource } = useGridData(data, {
getRowId: (row) => row.id,
});
dataSource.updateCell(rowId, "fieldName", newValue);
```
### How do I refresh data from the server?
For server data sources, the grid automatically re-queries when sort/filter changes. For manual refresh, recreate the data source:
```tsx
const [key, setKey] = useState(0);
// Refresh
setKey(k => k + 1);
return ;
```
### Can I use async data loading?
Yes, use `createServerDataSource` which accepts an async function.
## Features
### Does gp-grid support column resizing?
Column resizing is on the roadmap. Currently, columns have fixed widths defined in the column definition.
### Does gp-grid support row grouping?
Row grouping is planned for a future release.
### Can I export to Excel/CSV?
Export functionality is not built-in. You can access the data source and implement export yourself:
```tsx
const exportToCSV = () => {
const rows = dataSource.getAllRows(); // Your implementation
// Convert to CSV and download
};
```
## Troubleshooting
### Grid shows no data
1. Check that `rowData` or `dataSource` is provided
2. Verify the container has explicit height
3. Check browser console for errors
### Cells don't render correctly
1. Ensure `field` in column definition matches property name in data
2. Check `cellDataType` matches actual data type
### Sorting doesn't work
1. Verify `sortingEnabled` is not set to `false`
2. Check column has `sortable: true` (default)
3. For server-side data, ensure server handles sort request
# Getting Started (React)
gp-grid provides official React bindings through the `@gp-grid/react` package.
## Quick Start
npm
pnpm
bun
yarn
```bash
npm install @gp-grid/react
```
```bash
pnpm add @gp-grid/react
```
```bash
bun add @gp-grid/react
```
```bash
yarn add @gp-grid/react
```
```tsx
import { Grid, type ColumnDefinition } from "@gp-grid/react";
const columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
];
const data = [
{ id: 1, name: "Giovanni", email: "giovanni@example.com" },
{ id: 2, name: "Luca", email: "luca@example.com" },
];
function App() {
return (
);
}
```
## Features
All features are included out of the box — no enterprise tiers, no feature gating.
* [Column Dragging](/docs/react/examples/column-dragging) — reorder columns by dragging headers
* [Row Dragging](/docs/react/examples/row-dragging) — reorder rows with drag handle or entire row
* [Sorting](/docs/react/examples/sorting) — single and multi-column sorting
* [Filtering](/docs/react/examples/filtering) — column filtering with operators
* [Selection](/docs/react/examples/selection) — cell and range selection
* [Editing](/docs/react/examples/editing) — inline editing with fill handle
* [Custom Renderers](/docs/react/examples/custom-renderers) — custom cell, edit, and header renderers
* [Highlighting](/docs/react/examples/highlighting) — row, column, and cell highlighting
* [Live Data](/docs/react/examples/live-data) — real-time data mutations with streaming updates
## Next Steps
# Installation (React)
## Package Manager
Install [`@gp-grid/react`](https://www.npmjs.com/package/@gp-grid/react) using your preferred package manager:
npm
pnpm
bun
yarn
```bash
npm install @gp-grid/react
```
```bash
pnpm add @gp-grid/react
```
```bash
bun add @gp-grid/react
```
```bash
yarn add @gp-grid/react
```
## Requirements
* React 18 or higher
* TypeScript 5.0+ (recommended)
## Peer Dependencies
The package has the following peer dependencies that should be installed automatically:
* `react` >= 18.0.0
* `react-dom` >= 18.0.0
## TypeScript
gp-grid is written in TypeScript and includes built-in type definitions. No additional `@types` packages are required.
```tsx
import { Grid, type ColumnDefinition, type GridProps } from "@gp-grid/react";
```
## Styles
Starting from `v0.11.0`, gp-grid no longer auto-injects its CSS to avoid [FUOC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content), this gives you the full flexibility of customizing gp-grid styles!.. You must import the stylesheet once in your application (e.g. in `main.tsx`, `App.tsx`, or your global CSS):
```tsx
import "@gp-grid/react/dist/styles.css";
```
Or with standard CSS import:
```css
@import "@gp-grid/react/dist/styles.css";
```
## Next Steps
Once installed, check out the [Basic Usage](/docs/react/basic-usage) guide to create your first grid.
# Basic Usage (Vue)
This guide covers the fundamentals of using gp-grid in your Vue 3 application.
Don't forget to import the stylesheet once at the root of your app (e.g. `main.ts`):
```ts
import "@gp-grid/vue/dist/styles.css";
```
## Minimal Example
```vue
```
## Container Sizing
The grid fills its parent container. Ensure the parent has explicit dimensions:
```vue
```
## Reactive Data
Use `ref` or `reactive` for reactive data:
```vue
```
## Column Definitions
```vue
```
### Cell Data Types
| Type | Description |
| ---------------- | ---------------------- |
| `text` | String values |
| `number` | Numeric values |
| `boolean` | True/false values |
| `date` | Date objects |
| `dateString` | Date as ISO string |
| `dateTime` | DateTime objects |
| `dateTimeString` | DateTime as ISO string |
| `object` | Complex objects |
## Dark Mode
Enable dark mode with the `dark-mode` prop:
```vue
```
### With VueUse
```vue
```
## Header Height & Overscan
```vue
```
## Next Steps
* Learn about [Sorting](/docs/vue/examples/sorting) and [Filtering](/docs/vue/examples/filtering)
* Explore [Custom Renderers](/docs/vue/examples/custom-renderers)
* See the complete [API Reference](/docs/vue/api)
# FAQ (Vue)
Frequently asked questions about @gp-grid/vue.
## General
### Does gp-grid work with Vue 2?
No, @gp-grid/vue requires Vue 3.3 or higher. Vue 2 is not supported.
### Does it work with Nuxt 3?
Yes, gp-grid works great with Nuxt 3. See the [Nuxt Integration](/docs/vue/nuxt) guide.
### Is TypeScript required?
No, but strongly recommended. gp-grid includes built-in TypeScript definitions.
## Reactivity
### Why isn't my grid updating?
1. **Using `shallowRef`**: You need to replace the entire array:
```ts
data.value = [...data.value, newItem]; // Works
data.value.push(newItem); // Won't trigger update
```
2. **Using `ref`**: Should work automatically, check console for errors.
3. **Using mutable data source**: Make sure to call the mutation methods:
```ts
dataSource.addRows([newItem]); // Correct
```
### How do I force a refresh?
```vue
```
## SSR / Nuxt
### Why do I get hydration errors?
gp-grid requires browser APIs. Wrap it in ``:
```vue
```
### How do I show a loading state during SSR?
```vue
```
## Styling
### How do I enable dark mode?
Use the `dark-mode` prop:
```vue
```
For integration with VueUse or Nuxt Color Mode, see the [Styling Guide](/docs/vue/guides/styling).
### How do I override styles in scoped CSS?
Use `:deep()` for scoped style overrides:
```vue
```
For complete styling options, see the [Styling Guide](/docs/vue/guides/styling).
## Performance
### Is there a row limit?
No practical limit. We've tested with 2 million rows. Performance is constant due to virtual scrolling.
### Why is initial render slow?
* Generate data outside the component or use `useMemo`
* Use `shallowRef` for large arrays
* Check for expensive custom renderers
# Getting Started (Vue)
gp-grid provides official Vue 3 bindings through the `@gp-grid/vue` package.
## Quick Start
npm
pnpm
bun
yarn
```bash
npm install @gp-grid/vue
```
```bash
pnpm add @gp-grid/vue
```
```bash
bun add @gp-grid/vue
```
```bash
yarn add @gp-grid/vue
```
```vue
```
## Features
All features are included out of the box — no enterprise tiers, no feature gating.
* [Column Dragging](/docs/vue/examples/column-dragging) — reorder columns by dragging headers
* [Row Dragging](/docs/vue/examples/row-dragging) — reorder rows with drag handle or entire row
* [Sorting](/docs/vue/examples/sorting) — single and multi-column sorting
* [Filtering](/docs/vue/examples/filtering) — column filtering with operators
* [Selection](/docs/vue/examples/selection) — cell and range selection
* [Editing](/docs/vue/examples/editing) — inline editing with fill handle
* [Custom Renderers](/docs/vue/examples/custom-renderers) — custom cell, edit, and header renderers
* [Highlighting](/docs/vue/examples/highlighting) — row, column, and cell highlighting
## Next Steps
# Installation (Vue)
## Package Manager
Install [`@gp-grid/vue`](https://www.npmjs.com/package/@gp-grid/vue) using your preferred package manager:
npm
pnpm
bun
yarn
```bash
npm install @gp-grid/vue
```
```bash
pnpm add @gp-grid/vue
```
```bash
bun add @gp-grid/vue
```
```bash
yarn add @gp-grid/vue
```
## Requirements
* Vue 3.3 or higher
* TypeScript 5.0+ (recommended)
## Peer Dependencies
The package has the following peer dependencies:
* `vue` >= 3.3.0
## TypeScript
gp-grid is written in TypeScript and includes built-in type definitions:
```vue
```
## Styles
Starting from `v0.11.0`, gp-grid no longer auto-injects its CSS to avoid [FUOC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content), this gives you the full flexibility of customizing gp-grid styles!. You must import the stylesheet once in your application (e.g. in `main.ts` or a global CSS file):
```ts
import "@gp-grid/vue/dist/styles.css";
```
Or with standard CSS import:
```css
@import "@gp-grid/vue/dist/styles.css";
```
## Vite Configuration
No special configuration needed. The package works out of the box with Vite.
## Next Steps
* See [Basic Usage](/docs/vue/basic-usage) for your first grid
* Check [Nuxt Integration](/docs/vue/nuxt) for Nuxt-specific setup
# Nuxt Integration (Vue)
gp-grid works seamlessly with Nuxt 3. This guide covers Nuxt-specific setup and patterns.
## Installation
```bash
pnpm add @gp-grid/vue
```
## Basic Usage
Create a component or use directly in pages:
```vue
```
## Client-Only Rendering
Since gp-grid requires browser APIs, wrap it in `` for SSR:
```vue
Loading grid...
```
## Dark Mode with Color Mode
Using `@nuxtjs/color-mode`:
```vue
```
## Server-Side Data with API Routes
### API Route
```ts
// server/api/grid-data.post.ts
import { defineEventHandler, readBody } from "h3";
export default defineEventHandler(async (event) => {
const { range, sort, filter } = await readBody(event);
// Query your database (endRow is exclusive)
const results = await db.query({
offset: range.startRow,
limit: range.endRow - range.startRow,
orderBy: sort,
where: filter,
});
return {
rows: results.data,
totalRows: results.total,
};
});
```
### Component
```vue
```
## TypeScript Configuration
Ensure TypeScript is configured in `nuxt.config.ts`:
```ts
export default defineNuxtConfig({
typescript: {
strict: true,
},
});
```
# Column Definition (Angular)
Angular uses `AngularColumnDefinition`, which extends the core `ColumnDefinition` to allow `TemplateRef` references as renderers in addition to functions and string keys.
```ts
interface AngularColumnDefinition {
field: string;
colId?: string;
cellDataType: CellDataType;
width: number;
headerName?: string;
editable?: boolean;
sortable?: boolean;
filterable?: boolean;
hidden?: boolean;
resizable?: boolean;
minWidth?: number;
maxWidth?: number;
movable?: boolean;
rowDrag?: boolean;
cellRenderer?: string | TemplateRef<{ $implicit: CellRendererParams }> | ((params: CellRendererParams) => unknown);
editRenderer?: string | TemplateRef<{ $implicit: EditRendererParams }> | ((params: EditRendererParams) => unknown);
headerRenderer?: string | TemplateRef<{ $implicit: HeaderRendererParams }> | ((params: HeaderRendererParams) => unknown);
valueFormatter?: (value: CellValue) => string;
distinctValues?: CellValue[];
computeColumnClasses?: (context: HighlightContext) => string[];
computeCellClasses?: (context: HighlightContext) => string[];
}
```
## Required Properties
### field
**Type:** `string`
Property name in the row data object.
### cellDataType
**Type:** `CellDataType`
| Value | Description |
| ------------------ | -------------------- |
| `"text"` | String values |
| `"number"` | Numeric values |
| `"boolean"` | Boolean values |
| `"date"` | Date objects |
| `"dateString"` | ISO date strings |
| `"dateTime"` | DateTime objects |
| `"dateTimeString"` | ISO DateTime strings |
| `"object"` | Complex objects |
### width
**Type:** `number`
Column width in pixels.
## Optional Properties
### colId
**Type:** `string`
**Default:** Same as `field`
Unique identifier for the column.
### headerName
**Type:** `string`
**Default:** Same as `field`
Display name shown in the column header.
### editable
**Type:** `boolean`
**Default:** `false`
Enable inline editing for this column.
### sortable
**Type:** `boolean`
**Default:** `true`
Enable sorting for this column.
### filterable
**Type:** `boolean`
**Default:** `true`
Enable filtering for this column.
### hidden
**Type:** `boolean`
**Default:** `false`
Whether the column is hidden.
### resizable
**Type:** `boolean`
**Default:** `true`
Whether the column can be resized.
### minWidth / maxWidth
**Type:** `number`
**Default:** `50` / `undefined`
Minimum and maximum width constraints when resizing.
### movable
**Type:** `boolean`
**Default:** `true`
Whether the column can be reordered by dragging its header.
### rowDrag
**Type:** `boolean`
**Default:** `false`
Whether this column acts as a drag handle for row dragging.
## Renderers (Angular-specific)
Angular supports three renderer shapes in column definitions:
1. **Inline function** — same as React / Vue
2. **String key** — looks up the renderer from the component's `[cellRenderers]` / `[editRenderers]` / `[headerRenderers]` registry
3. **`TemplateRef`** — passed directly from the component template
### Using a TemplateRef
```ts
@Component({
selector: "app-my-grid",
standalone: true,
imports: [GpGridComponent],
template: `
{{ params.value }}
`,
})
export class MyGridComponent {
@ViewChild("statusCell", { static: true }) statusCell!: TemplateRef<{ $implicit: CellRendererParams }>;
columns: AngularColumnDefinition[] = [
{ field: "status", cellDataType: "text", width: 120, cellRenderer: "status" },
];
}
```
You can also reference a `TemplateRef` directly in a column definition without the registry:
```ts
@Component({
template: `
${{ (params.value as number).toLocaleString() }}
`,
})
export class MyGridComponent {
getColumns(salaryCell: TemplateRef<{ $implicit: CellRendererParams }>): AngularColumnDefinition[] {
return [
{ field: "salary", cellDataType: "number", width: 140, cellRenderer: salaryCell },
];
}
}
```
## valueFormatter
**Type:** `(value: CellValue) => string`
Converts a raw cell value into the string the grid treats as the cell's **display value**, used by the default renderer and copy to clipboard.
Filtering uses it selectively: the values (checkbox) filter mode groups its entries by formatted label — ticking a label selects every raw value that renders as it, and the filter model (including requests sent to a server data source) always carries the **raw** values. Free-text conditions (`contains`, `equals`, ...) compare against the formatted string so users can type what they see. Sorting compares raw values and does not use the formatter.
When a formatter collapses several raw values into one label on a high-cardinality column, pre-supply the full raw domain via `distinctValues` so the values filter can reach every raw value; the grid warns in the console when its distinct-value scan is cut short for a formatted column.
```ts
{
field: "salary",
cellDataType: "number",
width: 140,
valueFormatter: (value) => `$${(value as number).toLocaleString()}`,
}
```
Inside a `cellRenderer` the `params.value` you receive is already the *formatted* string when a `valueFormatter` is set.
## computeColumnClasses / computeCellClasses
Per-column overrides for the grid-level `highlighting` callbacks. See the [Styling guide](/docs/angular/guides/styling) for full context shape.
```ts
{
field: "score",
cellDataType: "number",
width: 100,
computeCellClasses: (ctx) => {
const value = ctx.rowData?.score as number;
if (value != null && value < 60) return ["bg-red-50"];
if (value != null && value >= 90) return ["bg-green-50"];
return [];
},
}
```
# Data Sources (Angular)
gp-grid provides the same data source abstractions across bindings. Angular consumers have two ergonomic entry points on top of the core factories: an injectable `GridDataService` for component-scoped DI, and a plain `createGridData()` helper for when DI is not a fit.
## Basic Usage with rowData
The simplest approach is to pass an array through the `rowData` input:
```ts
@Component({
selector: "app-my-grid",
standalone: true,
imports: [GpGridComponent],
template: `
// [!code highlight]
`,
})
export class MyGridComponent {
data = [
{ id: 1, name: "Giovanni" },
{ id: 2, name: "Luca" },
];
}
```
This internally creates a client data source.
## Client Data Source
For more control — custom field accessors, disabling the Web Worker, etc. — use `createClientDataSource`:
```ts
import { GpGridComponent, createClientDataSource } from "@gp-grid/angular";
@Component({
standalone: true,
imports: [GpGridComponent],
template: ``,
})
export class MyGridComponent {
dataSource = createClientDataSource(largeDataset, {
useWorker: true,
});
}
```
### Options
| Option | Default | Description |
| --------------- | ------- | ------------------------------------------- |
| `getFieldValue` | – | Custom accessor for nested properties |
| `useWorker` | `true` | Use a Web Worker for sorting large datasets |
## Mutable Data Source
For CRUD operations and real-time updates inside an Angular component, prefer the **injectable `GridDataService`** via `provideGridData()` / `injectGridData()`. It wraps `createMutableClientDataSource` with automatic cleanup on component destroy and gives every `@Component` its own isolated instance.
```ts title="my-grid.component.ts" {9-12,18,24-26}
import { Component } from "@angular/core";
import {
GpGridComponent,
provideGridData,
injectGridData,
type ColumnDefinition,
} from "@gp-grid/angular";
interface Person {
id: number;
name: string;
age: number;
}
@Component({
selector: "app-my-grid",
standalone: true,
imports: [GpGridComponent],
providers: [
provideGridData({
getRowId: (row) => row.id,
initialData: seedRows,
debounceMs: 50,
}),
],
template: `
`,
})
export class MyGridComponent {
protected readonly grid = injectGridData();
columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80 },
{ field: "name", cellDataType: "text", width: 200 },
{ field: "age", cellDataType: "number", width: 100 },
];
addOne(): void {
this.grid.addRows([{ id: Date.now(), name: "Mario", age: 30 }]);
}
renameFirst(): void {
this.grid.updateCell(1, "name", "Maria");
}
}
```
`provideGridData()` must be registered in the **component's** `providers` array, not in a module or a parent component. Registering it higher up would silently share one data source between every child — which is usually a bug. One grid component, one `provideGridData()` call.
### Function-based alternative: `createGridData`
When you want a mutable data source *without* going through DI — for example in a service that manages several grids, or in a standalone utility — reach for `createGridData()`. It returns the same API object shape, minus the automatic cleanup:
```ts
import { createGridData } from "@gp-grid/angular";
@Component({ /* ... */ })
export class MyGridComponent implements OnDestroy {
protected readonly grid = createGridData(seedRows, {
getRowId: (row) => row.id,
});
ngOnDestroy(): void {
this.grid.clear(); // [!code highlight]
}
}
```
Unlike `GridDataService`, `createGridData()` does **not** dispose of the underlying data source when the component is destroyed. Call `clear()` (or tear it down yourself) in `ngOnDestroy` to avoid leaking workers and subscriptions.
### Escape hatch: raw factory
Reach for the raw `createMutableClientDataSource` factory only when neither path above fits — module-level singletons, shared data sources used by multiple unrelated components, or non-Angular code. In a component, always prefer `provideGridData` / `createGridData`.
```ts
// Outside a component (e.g. a shared module)
import { createMutableClientDataSource } from "@gp-grid/angular";
export const sharedDataSource = createMutableClientDataSource(seed, {
getRowId: (row) => row.id,
});
```
### Transaction System
The mutable data source batches mutations through an internal transaction system:
* **Automatic batching** — operations within the debounce window collapse into a single transaction, minimising re-renders.
* **Optimistic processing** — mutations are queued asynchronously so the UI stays responsive under heavy churn.
* **Flush control** — call `flushTransactions()` to force immediate processing (useful before navigating away or running assertions).
```ts
await this.grid.flushTransactions();
```
### Options
```ts
interface GridDataOptions {
initialData: TData[]; // Required: initial rows
getRowId: (row: TData) => RowId; // Required: unique ID accessor
debounceMs?: number; // Batch window (default: 50)
useWorker?: boolean; // Worker sort (default: true)
parallelSort?: ParallelSortOptions | false;
}
```
### GridDataService Interface
The injected service exposes the same surface as the React hook and Vue composable, plus the `dataSource` itself:
```ts
class GridDataService {
readonly dataSource: MutableDataSource;
addRows(rows: TData[]): void;
removeRows(ids: RowId[]): void;
updateRow(id: RowId, data: Partial): void;
updateCell(id: RowId, field: string, value: CellValue): void;
clear(): void;
getRowById(id: RowId): TData | undefined;
getTotalRowCount(): number;
flushTransactions(): Promise;
}
```
### Use Cases
The mutable data source is ideal for:
* **Real-time dashboards** — live feeds with frequent updates
* **Streaming data** — WebSocket or SSE pipelines
* **User editing** — forms and inline editing with immediate feedback
* **Bulk operations** — imports, mass updates, batch delete
## DataSource Interface
All data sources implement the `DataSource` interface. Both `createClientDataSource` and `createServerDataSource` return objects that conform to it; `MutableDataSource` extends it with mutation methods.
```ts
interface DataSource {
readonly loadMode?: "all" | "paginated";
query(request: DataSourceRequest): Promise>;
destroy?(): void;
moveRow?(fromIndex: number, toIndex: number): void;
}
```
| Member | Required | Description |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `loadMode` | No | `"all"` (default for client data sources) or `"paginated"` (default for server). Controls whether the grid requests every row at once or only the visible window via `range.startRow` / `range.endRow`. |
| `query` | Yes | Async function returning `{ rows, totalRows }` for the requested range, sort, and filter. Called by the grid whenever the visible window, sort, or filter changes. |
| `destroy` | No | Cleanup hook for releasing workers, sockets, or other resources. Called when the data source is detached from the grid. |
| `moveRow` | No | Called on row drag-end. Implement it to update the underlying data; the grid does not mutate data on its own. |
## Server Data Source
For server-side sorting, filtering, and windowed row loading:
```ts
import { GpGridComponent, createServerDataSource } from "@gp-grid/angular";
@Component({
standalone: true,
imports: [GpGridComponent],
template: ``,
})
export class MyGridComponent {
dataSource = createServerDataSource(async (request) => { // [!code highlight]
const { range, sort, filter } = request;
const response = await fetch("/api/data", {
method: "POST",
body: JSON.stringify({
startRow: range.startRow,
endRow: range.endRow,
sortBy: sort,
filters: filter,
}),
});
const result = await response.json();
return { rows: result.data, totalRows: result.total };
});
}
```
Pass `{ loadMode: "all" }` as a second argument if the server returns the entire result set in one call. The default is `"paginated"` — the grid only requests the row window currently in view via `range.startRow` / `range.endRow`.
```ts
const dataSource = createServerDataSource(queryFn, { loadMode: "paginated" });
```
### Request / Response
```ts
interface DataSourceRequest {
range: {
startRow: number; // First row index to fetch (0-indexed, inclusive)
endRow: number; // First row index after the range (exclusive)
};
sort?: SortModel[];
filter?: FilterModel;
// Per-field formatters, used by client-side data sources for free-text
// conditions. Functions are not serializable — servers can ignore them.
valueFormatters?: Record string>;
}
interface DataSourceResponse {
rows: TData[];
totalRows: number;
}
```
Values-mode text filters arrive with `selectedValues` as a `Set` of **raw** cell values — a `valueFormatter` on the column never changes what the server receives. When forwarding the request as JSON, spread each Set into an array first: `JSON.stringify` turns a `Set` into `{}`.
## Choosing a Data Source
| Scenario | Recommended |
| ----------------------------------------------------------- | ------------------------------------ |
| Small dataset (under 10k rows) | `rowData` input |
| Large dataset, client-side | `createClientDataSource` |
| Editable data with CRUD, inside a component | `provideGridData` + `injectGridData` |
| Editable data, outside DI (service, shared utility) | `createGridData` |
| Editable data, shared singleton across unrelated components | `createMutableClientDataSource` |
| Server-side operations | `createServerDataSource` |
# Grid Props (Angular)
The `GpGridComponent` is a standalone Angular component with `OnPush` change detection. Binder syntax is standard Angular: `[inputName]` for inputs and `(outputName)` for outputs.
## Required Inputs
### \[columns]
**Type:** `AngularColumnDefinition[]`
Array of column definitions. See [Column Definition](/docs/angular/api/column-definition) for details.
```ts
@Component({
standalone: true,
imports: [GpGridComponent],
template: ``,
})
export class MyGridComponent {
columns: AngularColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80 },
{ field: "name", cellDataType: "text", width: 200 },
];
}
```
### \[rowHeight]
**Type:** `number`
Height of each row in pixels.
```ts
```
## Data Inputs
### \[rowData]
**Type:** `TData[]`
Array of row data objects. The simplest way to provide data.
```ts
```
### \[dataSource]
**Type:** `DataSource`
Data source for advanced data handling. Use this for server-side data or when you need mutation capabilities. See [Data Sources](/docs/angular/api/data-sources).
```ts
```
## Optional Inputs
### \[headerHeight]
**Type:** `number`
**Default:** Same as `[rowHeight]`
Height of the header row in pixels.
```ts
```
### \[overscan]
**Type:** `number`
**Default:** `3`
Number of rows to render outside the visible viewport.
```ts
```
### \[sortingEnabled]
**Type:** `boolean`
**Default:** `true`
Enable or disable sorting globally.
```ts
```
### \[darkMode]
**Type:** `boolean`
**Default:** `false`
Enable dark mode styling.
```ts
```
### \[wheelDampening]
**Type:** `number`
**Default:** `0.1`
Dampening factor for wheel scrolling (0–1).
Enabling this input activates **smooth scrolling** for grids that exceed millions of rows by using virtual dampening, which keeps wheel-based navigation fluid on very large datasets.
```ts
```
### \[maxFlingVelocity]
**Type:** `number`
**Default:** `20 × rowHeight`
Maximum accumulated touch-fling velocity in logical px/ms when scroll virtualization is active. Pair higher values with `overscan` 10–12 for smoother fast scrolling on touch devices.
```ts
```
## Drag & Reorder Inputs / Outputs
### \[rowDragEntireRow]
**Type:** `boolean`
**Default:** `false`
When enabled, clicking and dragging any cell in a row initiates a row drag instead of cell selection.
### (onRowDragEnd)
**Type:** `{ source: number; target: number }`
Emitted when a row is dropped after dragging.
```ts
@Component({
template: `
`,
})
export class MyGridComponent {
handleRowDragEnd({ source, target }: { source: number; target: number }) {
this.dataSource.moveRow?.(source, target);
}
}
```
### (onColumnMoved)
**Type:** `{ fromIndex: number; toIndex: number }`
Emitted when a column header is dragged to a new position.
```ts
@Component({
template: `
`,
})
export class MyGridComponent {
handleColumnMoved({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) {
const next = [...this.columns];
const [moved] = next.splice(fromIndex, 1);
next.splice(toIndex, 0, moved);
this.columns = next;
}
}
```
### (onColumnResized)
**Type:** `{ colIndex: number; newWidth: number }`
Emitted when a column is resized by dragging the header edge.
## Identity & Edit Inputs / Outputs
### \[getRowId]
**Type:** `((row: TData) => RowId) | null`
Function to extract a unique ID from each row. Required when `(onCellValueChanged)` is used.
### (onCellValueChanged)
**Type:** `CellValueChangedEvent`
Emitted after a cell value is committed via inline editing or fill-handle drag.
```ts
@Component({
template: `
`,
})
export class MyGridComponent {
idGetter = (row: MyRow) => row.id;
handleCellValueChanged(event: CellValueChangedEvent) {
console.log(`Row ${event.rowId} changed ${event.oldValue} → ${event.newValue}`);
}
}
```
## Styling Inputs
### \[highlighting]
**Type:** `HighlightingOptions | null`
Dynamic row, column, and cell class computation. See the [Styling guide](/docs/angular/guides/styling).
```ts
@Component({
template: `
`,
})
export class MyGridComponent {
highlightingOptions: HighlightingOptions = {
computeRowClasses: (ctx) =>
ctx.rowIndex != null && ctx.rowIndex % 2 === 1 ? ["zebra-row"] : [],
computeCellClasses: (ctx) =>
ctx.isSelected ? ["bg-blue-50"] : [],
};
}
```
## Renderer Inputs
### \[cellRenderers]
**Type:** `Record`
Registry of named cell renderers. Reference by key in column definitions.
### \[editRenderers]
**Type:** `Record`
Registry of named edit renderers.
### \[headerRenderers]
**Type:** `Record`
Registry of named header renderers.
### \[cellRenderer]
**Type:** `CellRendererTemplate | null`
Global cell renderer for all cells.
### \[editRenderer]
**Type:** `EditRendererTemplate | null`
Global edit renderer.
### \[headerRenderer]
**Type:** `HeaderRendererTemplate | null`
Global header renderer.
## Template Ref
You can access the underlying component instance (and its `core` property) via a template ref:
```ts
@Component({
template: `
`,
})
export class MyGridComponent {
sortByName(grid: GpGridComponent) {
grid.vm.coreRef?.setSort("name", "asc");
}
}
```
# API Overview (Angular)
Complete API documentation for `@gp-grid/angular`.
All inputs accepted by the Grid component
Configure individual columns
Client-side and server-side data handling
The Angular reference is still being fleshed out. The shape of `GridProps` and `ColumnDefinition` matches the other bindings — see the [React API Reference](/docs/react/api) for anything not yet documented here.
## Exports
```ts
// Component
import { GpGridComponent } from "@gp-grid/angular";
// Types
import type {
AngularColumnDefinition,
CellRendererTemplate,
EditRendererTemplate,
HeaderRendererTemplate,
ColumnDefinition,
CellDataType,
CellValue,
CellPosition,
CellRange,
SortDirection,
SortModel,
FilterModel,
DataSource,
DataSourceRequest,
DataSourceResponse,
MutableDataSource,
CellRendererParams,
EditRendererParams,
HeaderRendererParams,
HighlightingOptions,
} from "@gp-grid/angular";
// Data source factories
import {
createClientDataSource,
createDataSourceFromArray,
createMutableClientDataSource,
createServerDataSource,
} from "@gp-grid/angular";
// Grid data helpers
import {
provideGridData, // component providers for DI-based data
injectGridData, // typed inject() helper
GridDataService, // the underlying service
createGridData, // function-based alternative (no DI)
} from "@gp-grid/angular";
// Data options / API types
import type {
GridDataOptions,
GridDataApi,
} from "@gp-grid/angular";
```
# Column Dragging (Angular)
Reorder columns by dragging their headers.
## Interactive Demo
Angular-specific code samples below.
```ts title="draggable-columns-grid.component.ts" {14}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-draggable-columns-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class DraggableColumnsGridComponent {
columns: ColumnDefinition[] = [/* ... */];
data = [/* ... */];
}
```
# Custom Renderers (Angular)
Customize how cells and headers are rendered with custom renderer functions.
## Interactive Demo
This demo shows three custom renderers in action:
* **Status** - Colored dot indicator (green/amber/gray)
* **Performance** - Positive values in green, negative in red
* **Salary** - Formatted as currency
Angular-specific code samples below. The renderer signature is the same across all three framework bindings.
Free-text filter conditions and copy-paste operate on the **formatted value** — the string users actually see — while the values (checkbox) filter and sorting work on **raw** values. A `cellRenderer` only changes how a cell is *painted*; it does not affect filtering at all. Pair every custom renderer with a matching `valueFormatter` so the text users type into a filter matches what they see:
```ts
{
field: "salary",
cellDataType: "number",
width: 140,
cellRenderer: salaryRenderer,
valueFormatter: (value) => `$${(value as number).toLocaleString()}`,
}
```
Without the formatter, users typing `"$82,000"` into the Salary column's text condition would match nothing because the comparison would run against the raw `82000`. See the [React `valueFormatter` reference](/docs/react/api/column-definition#valueformatter) for signature, caveats, and more examples — the Angular API is identical.
## Cell Renderers
```ts title="renderer-grid.component.ts" {35}
import { Component } from "@angular/core";
import {
GridComponent,
type ColumnDefinition,
type CellRendererParams,
} from "@gp-grid/angular";
const statusRenderer = (params: CellRendererParams) => {
const status = params.value as string;
const color =
status === "ok" ? "#22c55e" : status === "warn" ? "#f59e0b" : "#9ca3af";
return ``;
};
@Component({
selector: "app-renderer-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class RendererGridComponent {
columns: ColumnDefinition[] = [
{
field: "status",
cellDataType: "text",
width: 80,
cellRenderer: statusRenderer,
},
];
data = [/* your data */];
}
```
# Editing (Angular)
This Angular example is being written. See the [React editing example](/docs/react/examples/editing) for the conceptual model.
## Interactive Demo
```ts title="editable-grid.component.ts" {20-21}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-editable-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class EditableGridComponent {
columns: ColumnDefinition[] = [
{ field: "name", cellDataType: "text", width: 150, editable: true },
{ field: "salary", cellDataType: "number", width: 120, editable: true },
];
data = [/* your data */];
}
```
# Filtering (Angular)
This Angular example is being written. See the [React filtering example](/docs/react/examples/filtering) for the conceptual model — the `filterable` column flag and filter operators behave identically across bindings.
```ts title="filterable-grid.component.ts" {20-21}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-filterable-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class FilterableGridComponent {
columns: ColumnDefinition[] = [
{ field: "name", cellDataType: "text", width: 150, filterable: true },
{ field: "salary", cellDataType: "number", width: 120, filterable: true },
];
data = [/* your data */];
}
```
# Highlighting (Angular)
This example demonstrates the highlighting API using `computeRowClasses`, `computeColumnClasses`, and `computeCellClasses` to create dynamic hover effects.
## Interactive Demo
Use the toggle buttons to switch between different highlighting modes:
Angular-specific code samples below. The callback signatures are identical across framework bindings.
## Row Highlighting
```ts title="highlighted-grid.component.ts" {14,23-26}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-highlighted-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class HighlightedGridComponent {
columns: ColumnDefinition[] = [/* ... */];
data = [/* ... */];
computeRowClasses = ({ hoveredRowIndex, rowIndex }: {
hoveredRowIndex: number | null;
rowIndex: number;
}) => (rowIndex === hoveredRowIndex ? "row-highlight" : "");
}
```
# Examples (Angular)
Explore code examples demonstrating gp-grid's features with Angular.
# Row Dragging (Angular)
Reorder rows with a drag handle or by dragging the entire row.
## Interactive Demo
Angular-specific code samples below.
```ts title="draggable-rows-grid.component.ts" {14}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-draggable-rows-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class DraggableRowsGridComponent {
columns: ColumnDefinition[] = [/* ... */];
data = [/* ... */];
}
```
# Selection (Angular)
This Angular example is being written. See the [React selection example](/docs/react/examples/selection) for the conceptual model.
```ts title="selectable-grid.component.ts" {14}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-selectable-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class SelectableGridComponent {
columns: ColumnDefinition[] = [/* ... */];
data = [/* ... */];
}
```
# Sorting (Angular)
gp-grid supports single and multi-column sorting.
## Basic Sorting
Sorting is enabled by default. Click a column header to sort:
```ts title="sortable-grid.component.ts" {14}
import { Component } from "@angular/core";
import { GridComponent, type ColumnDefinition } from "@gp-grid/angular";
@Component({
selector: "app-sortable-grid",
standalone: true,
imports: [GridComponent],
template: `
`,
})
export class SortableGridComponent {
columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
{ field: "salary", cellDataType: "number", width: 120, headerName: "Salary" },
];
data = [/* your data */];
}
```
## Multi-Column Sorting
Hold `Shift` while clicking headers to sort by multiple columns.
## Disable Sorting
### Globally
```ts
template: `
`
```
### Per Column
```ts
columns: ColumnDefinition[] = [
// [!code highlight]
{ field: "id", cellDataType: "number", width: 80, sortable: false },
{ field: "name", cellDataType: "text", width: 150, sortable: true },
];
```
## Sort Indicators
The header displays sort direction:
* `▲` - Ascending
* `▼` - Descending
* Number badge for multi-column sort order
# Guides Overview (Angular)
# Performance (Angular)
Angular performance tips are being written. See the [React performance guide](/docs/react/guides/performance) — most advice (virtualization, overscan, cell renderer purity) applies verbatim.
## Smooth Scrolling for Million-Row Grids
Enable virtual dampening to keep wheel scrolling smooth on grids that exceed millions of rows:
```ts
```
The `[wheelDampening]` input accepts a value between `0` and `1`. Lower values produce smoother scrolling but travel less distance per wheel event, which is ideal when precise navigation matters more than raw speed.
For touch devices, pair `[wheelDampening]` with `[maxFlingVelocity]` to control scroll momentum after a flick gesture:
```ts
```
The default `[maxFlingVelocity]` is `20 × rowHeight`. Raise it together with `overscan` (10–12 recommended) to make fast touch-flicking feel more responsive.
# Styling (Angular)
Angular styling docs are being written. See the [React styling guide](/docs/react/guides/styling) — the underlying CSS variables and class hooks are the same.
# Column Definition (React)
The `ColumnDefinition` interface defines how each column behaves and appears.
```tsx
interface ColumnDefinition {
field: string;
colId?: string;
cellDataType: CellDataType;
width: number;
headerName?: string;
editable?: boolean;
sortable?: boolean;
filterable?: boolean;
hidden?: boolean;
resizable?: boolean;
minWidth?: number;
maxWidth?: number;
movable?: boolean;
rowDrag?: boolean;
cellRenderer?: string | ((params: CellRendererParams) => ReactNode);
editRenderer?: string | ((params: EditRendererParams) => ReactNode);
headerRenderer?: string | ((params: HeaderRendererParams) => ReactNode);
valueFormatter?: (value: CellValue) => string;
/** Pre-supplied raw values for the values filter mode (skips the distinct-value scan). */
distinctValues?: CellValue[];
/** Per-column override for column-level highlighting. Overrides grid-level computeColumnClasses for this column. */
computeColumnClasses?: (context: HighlightContext) => string[];
/** Per-column override for cell-level highlighting. Overrides grid-level computeCellClasses for cells in this column. */
computeCellClasses?: (context: HighlightContext) => string[];
}
```
## Required Properties
### field
**Type:** `string`
The property name in the row data object.
```tsx
// For data like { id: 1, firstName: "Giovanni" }
{ field: "firstName", cellDataType: "text", width: 150 }
```
### cellDataType
**Type:** `CellDataType`
The data type of the cell. Used for formatting, sorting, and filtering.
| Value | Description |
| ------------------ | -------------------- |
| `"text"` | String values |
| `"number"` | Numeric values |
| `"boolean"` | Boolean values |
| `"date"` | Date objects |
| `"dateString"` | ISO date strings |
| `"dateTime"` | DateTime objects |
| `"dateTimeString"` | ISO DateTime strings |
| `"object"` | Complex objects |
### width
**Type:** `number`
Column width in pixels.
```tsx
{ field: "email", cellDataType: "text", width: 250 }
```
## Optional Properties
### colId
**Type:** `string`
**Default:** Same as `field`
Unique identifier for the column. Useful when multiple columns use the same field.
```tsx
{
field: "price",
colId: "originalPrice",
cellDataType: "number",
width: 100
}
```
### headerName
**Type:** `string`
**Default:** Same as `field`
Display name shown in the column header.
```tsx
{
field: "firstName",
headerName: "First Name",
cellDataType: "text",
width: 150
}
```
### editable
**Type:** `boolean`
**Default:** `false`
Enable inline editing for this column.
```tsx
{
field: "email",
cellDataType: "text",
width: 250,
editable: true
}
```
### sortable
**Type:** `boolean`
**Default:** `true` (when `sortingEnabled` is true on Grid)
Enable sorting for this column.
```tsx
{
field: "id",
cellDataType: "number",
width: 80,
sortable: false // Disable sorting for this column
}
```
### filterable
**Type:** `boolean`
**Default:** `true`
Enable filtering for this column.
```tsx
{
field: "status",
cellDataType: "text",
width: 100,
filterable: false
}
```
### hidden
**Type:** `boolean`
**Default:** `false`
Whether the column is hidden. Hidden columns are not rendered but still exist in the definition.
```tsx
{
field: "internalId",
cellDataType: "text",
width: 100,
hidden: true // Column exists but isn't visible
}
```
### resizable
**Type:** `boolean`
**Default:** `true`
Whether the column can be resized by dragging the header edge.
```tsx
{
field: "id",
cellDataType: "number",
width: 80,
resizable: false // Fixed width
}
```
### minWidth
**Type:** `number`
**Default:** `50`
Minimum width in pixels when resizing.
### maxWidth
**Type:** `number`
**Default:** `undefined` (no limit)
Maximum width in pixels when resizing.
```tsx
{
field: "name",
cellDataType: "text",
width: 150,
minWidth: 80,
maxWidth: 400
}
```
### movable
**Type:** `boolean`
**Default:** `true`
Whether the column can be moved/reordered by dragging its header.
```tsx
{
field: "id",
cellDataType: "number",
width: 80,
movable: false // Cannot be reordered
}
```
### rowDrag
**Type:** `boolean`
**Default:** `false`
Whether this column acts as a drag handle for row dragging. When set to `true`, clicking and dragging cells in this column initiates a row drag.
```tsx
{
field: "handle",
cellDataType: "text",
width: 40,
rowDrag: true,
sortable: false
}
```
### cellRenderer
**Type:** `string | ((params: CellRendererParams) => ReactNode)`
Custom cell renderer. Pass a function directly or a string key to look up from the `cellRenderers` registry.
```tsx
// Inline function — simplest approach
{
field: "status",
cellDataType: "text",
width: 100,
cellRenderer: (params) => (
{params.value as string}
)
}
// String key — reference from cellRenderers registry
{ field: "status", cellDataType: "text", width: 100, cellRenderer: "status" }
```
### editRenderer
**Type:** `string | ((params: EditRendererParams) => ReactNode)`
Custom edit renderer. Pass a function directly or a string key to look up from the `editRenderers` registry.
### headerRenderer
**Type:** `string | ((params: HeaderRendererParams) => ReactNode)`
Custom header renderer. Pass a function directly or a string key to look up from the `headerRenderers` registry.
### valueFormatter
**Type:** `(value: CellValue) => string`
Converts a raw cell value into the string the grid treats as the cell's **display value**. The formatter runs before the default cell renderer paints the cell, so the output is what the user sees on screen.
Beyond display, each feature uses the formatter in a specific way:
* **Values (checkbox) filter mode** — the popup groups its checkbox entries by formatted label; ticking a label selects every raw value that renders as that label. The filter model itself — and any request sent to a server data source — always carries the **raw** values, never the labels.
* **Free-text filter conditions** (`contains`, `equals`, ...) — compared against the formatted string, so users can type exactly what they see in the cell.
* **Copy to clipboard** — copies the formatted string.
* **Sorting** — compares **raw** values; the formatter is not involved.
```tsx
{
field: "salary",
cellDataType: "number",
width: 140,
headerName: "Salary",
valueFormatter: (value) => `$${(value as number).toLocaleString()}`, // [!code highlight]
}
```
The signature takes the raw value directly (`(value) => ...`), not a params object. Inside a `cellRenderer` the `params.value` you receive is already the *formatted* string when a `valueFormatter` is set — read `params.rowData[column.field]` if the renderer needs the raw value.
#### Pairing with `cellRenderer`
A `cellRenderer` only changes how a cell is *painted*; it does not change the value used by filters or sort. If a renderer shows a string the raw value alone cannot produce (e.g. currency symbols, status icons, formatted dates), pair it with a `valueFormatter` so both stay in sync:
```tsx
{
field: "salary",
cellDataType: "number",
width: 140,
cellRenderer: SalaryRenderer,
valueFormatter: (value) => `$${(value as number).toLocaleString()}`, // [!code highlight]
}
```
Without the formatter, a user typing `"$82,000"` into a free-text condition would match nothing because the comparison would run against the raw `82000`.
#### Formatters that collapse values
Several raw values may format to the same label (e.g. dates formatted to their month). The values filter shows one checkbox per label, and ticking it selects all raw values behind it — but only the raw values the grid has discovered. On high-cardinality columns, pre-supply the full raw domain via `distinctValues` so no raw value is missed:
```tsx
{
field: "priority",
cellDataType: "number",
width: 120,
valueFormatter: (value) => (value as number) > 1 ? "High" : "Low",
distinctValues: [0, 1, 2, 3], // full raw domain, raw values only
}
```
The grid warns in the console when its distinct-value scan is cut short for a formatted column.
#### Common use cases
Formatting `"object"`-type columns where the default `JSON.stringify` is unhelpful:
```tsx
{
field: "address",
cellDataType: "object",
width: 250,
valueFormatter: (value) => {
const addr = value as { city: string; country: string };
return `${addr.city}, ${addr.country}`;
},
}
```
Date display with a library-free format:
```tsx
{
field: "createdAt",
cellDataType: "dateString",
width: 140,
valueFormatter: (value) => new Date(value as string).toLocaleDateString(),
}
```
Null-safe formatting:
```tsx
valueFormatter: (value) => value == null ? "—" : String(value),
```
### computeColumnClasses
**Type:** `(context: HighlightContext) => string[]`
Per-column override for column-level highlighting. If defined, it overrides the grid-level `computeColumnClasses` for this column. The context has `colIndex` set and `rowIndex` is `null`.
```tsx
{
field: "status",
cellDataType: "text",
width: 120,
computeColumnClasses: (context) =>
context.colIndex === 0 ? ["first-column"] : [],
}
```
### computeCellClasses
**Type:** `(context: HighlightContext) => string[]`
Per-column override for cell-level highlighting. If defined, it overrides the grid-level `computeCellClasses` for cells in this column. The context has both `rowIndex` and `colIndex` set.
```tsx
{
field: "score",
cellDataType: "number",
width: 100,
computeCellClasses: (context) => {
const value = context.rowData?.score as number;
if (value != null && value < 60) return ["bg-red-50"];
if (value != null && value >= 90) return ["bg-green-50"];
return [];
},
}
```
For full context shape, see the [Styling guide](/docs/react/guides/styling).
## Example
```tsx
const columns: ColumnDefinition[] = [
{
field: "id",
cellDataType: "number",
width: 80,
headerName: "ID",
sortable: true,
filterable: false,
editable: false,
},
{
field: "name",
cellDataType: "text",
width: 200,
headerName: "Full Name",
editable: true,
},
{
field: "salary",
cellDataType: "number",
width: 120,
headerName: "Salary",
editable: true,
cellRenderer: "currency",
},
{
field: "status",
cellDataType: "text",
width: 100,
headerName: "Status",
cellRenderer: "status",
editRenderer: "statusDropdown",
},
];
```
# Data Sources (React)
gp-grid provides a flexible data source abstraction for both client-side and server-side data handling.
## Basic Usage with rowData
The simplest approach is using the `rowData` prop directly:
```tsx
const data = [
{ id: 1, name: "Giovanni" },
{ id: 2, name: "Luca" },
];
```
This internally creates a client data source.
## Client Data Source
For more control, use `createClientDataSource`:
```tsx
import { Grid, createClientDataSource } from "@gp-grid/react";
const data = generateLargeDataset(1000000);
const dataSource = createClientDataSource(data);
```
### Options
```tsx
interface ClientDataSourceOptions {
getFieldValue?: (row: TData, field: string) => CellValue;
getValueFormatter?: (field: string) => ((v: CellValue) => string) | undefined;
useWorker?: boolean;
parallelSort?: ParallelSortOptions | false;
}
```
| Option | Default | Description |
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `getFieldValue` | - | Custom accessor for nested properties |
| `getValueFormatter` | - | Lookup for a field's valueFormatter so free-text conditions compare against the displayed value (values-mode selections compare raw values) |
| `useWorker` | `true` | Use Web Worker for sorting large datasets |
| `parallelSort` | - | Options for parallel sorting (only used when `useWorker` is true) |
### Nested Properties
```tsx
const data = [
{ id: 1, user: { name: "Giovanni", email: "g@example.com" } },
];
const dataSource = createClientDataSource(data, {
getFieldValue: (row, field) => {
if (field === "userName") return row.user.name;
if (field === "userEmail") return row.user.email;
return row[field as keyof typeof row];
},
});
```
## Mutable Data Source
For CRUD operations and real-time data updates, use the **`useGridData` hook** inside React components. It wraps `createMutableClientDataSource` with a stable reference across re-renders and exposes `addRows` / `removeRows` helpers bound to the current data source.
```tsx
import { useGridData } from "@gp-grid/react";
function MyGrid() {
const { dataSource, addRows, removeRows } = useGridData(initialData, {
getRowId: (row) => row.id, // Required: unique ID accessor
debounceMs: 50, // Batch updates (default: 50)
});
// Add rows
addRows([{ id: 3, name: "Mario" }]);
// Remove rows
removeRows([1, 2]);
// Update a cell
dataSource.updateCell(3, "name", "Maria");
// Update a row
dataSource.updateRow(3, { name: "Maria", email: "maria@example.com" });
// Force immediate processing
await dataSource.flushTransactions();
return ;
}
```
Reach for the raw `createMutableClientDataSource` factory only outside React components — module-level singletons, non-React code, or shared utilities. Inside a component, prefer `useGridData`: it handles subscription lifecycle, prevents stale closures, and keeps the dataSource reference stable across renders.
```tsx
// Outside a component (e.g. a shared module)
import { createMutableClientDataSource } from "@gp-grid/react";
export const sharedDataSource = createMutableClientDataSource(seed, {
getRowId: (row) => row.id,
});
```
### Transaction System
The mutable data source uses an internal transaction system that provides several key benefits:
**Automatic Batching** - Multiple operations within the debounce window are combined into a single transaction, minimizing re-renders and improving performance.
**Optimistic Processing** - Changes are queued and processed asynchronously, allowing the UI to remain responsive during bulk operations.
**Transaction Callbacks** - Track when transactions are processed by subscribing to the data source:
```tsx
const { dataSource } = useGridData(data, {
getRowId: (row) => row.id,
debounceMs: 50,
});
const unsubscribe = dataSource.subscribe((result) => {
console.log(`Added: ${result.added}, Removed: ${result.removed}, Updated: ${result.updated}`);
// Update external state, analytics, etc.
});
// Cleanup on unmount
useEffect(() => unsubscribe, []);
```
Or use the raw factory `createMutableClientDataSource` if you need a one-shot `onTransactionProcessed` callback:
```tsx
const dataSource = createMutableClientDataSource(seed, {
getRowId: (row) => row.id,
onTransactionProcessed: (result) => {
console.log(`Added: ${result.added}, Removed: ${result.removed}`);
},
});
```
**Flush Control** - Force immediate processing when needed:
```tsx
// Wait for all pending transactions
await dataSource.flushTransactions();
// Check if there are pending changes
if (dataSource.hasPendingTransactions()) {
// Handle pending state
}
```
### useGridData Hook Options
```tsx
interface UseGridDataOptions {
getRowId: (row: TData) => RowId; // Required: unique ID accessor
debounceMs?: number; // Batch window (default: 50)
useWorker?: boolean; // Use Web Worker for sorting (default: true)
parallelSort?: ParallelSortOptions | false;
}
```
### createMutableClientDataSource Factory Options
Use these options when creating a mutable data source outside a React component:
```tsx
interface MutableClientDataSourceOptions {
getRowId: (row: TData) => RowId; // Required: unique ID accessor
getFieldValue?: (row: TData, field: string) => CellValue;
getValueFormatter?: (field: string) => ((v: CellValue) => string) | undefined;
debounceMs?: number; // Batch window (default: 50)
onTransactionProcessed?: (result: TransactionResult) => void;
useWorker?: boolean; // Use Web Worker for sorting (default: true)
parallelSort?: ParallelSortOptions | false;
}
interface TransactionResult {
added: number; // Count of rows added
removed: number; // Count of rows removed
updated: number; // Count of rows updated
}
```
### MutableDataSource Interface
```tsx
interface MutableDataSource extends DataSource {
addRows(rows: TData[]): void;
removeRows(ids: RowId[]): void;
updateCell(id: RowId, field: string, value: CellValue): void;
updateRow(id: RowId, data: Partial): void;
flushTransactions(): Promise;
hasPendingTransactions(): boolean;
getDistinctValues(field: string): CellValue[];
getRowById(id: RowId): TData | undefined;
getTotalRowCount(): number;
subscribe(listener: DataChangeListener): () => void;
clear(): void;
moveRow(fromIndex: number, toIndex: number): void;
}
```
### Use Cases
The mutable data source is ideal for:
* **Real-time dashboards** - Live data feeds with frequent updates
* **Streaming data** - WebSocket or SSE data streams
* **User editing** - Forms and inline editing with immediate feedback
* **Bulk operations** - Import, batch updates, mass delete
See the [Live Data example](/docs/react/examples/live-data) for a complete implementation.
## DataSource Interface
All data sources implement the `DataSource` interface. Both `createClientDataSource` and `createServerDataSource` return objects that conform to it; `MutableDataSource` extends it with mutation methods.
```tsx
interface DataSource {
readonly loadMode?: "all" | "paginated";
query(request: DataSourceRequest): Promise>;
destroy?(): void;
moveRow?(fromIndex: number, toIndex: number): void;
}
```
| Member | Required | Description |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `loadMode` | No | `"all"` (default for client data sources) or `"paginated"` (default for server). Controls whether the grid requests every row at once or only the visible window via `range.startRow` / `range.endRow`. |
| `query` | Yes | Async function returning `{ rows, totalRows }` for the requested range, sort, and filter. Called by the grid whenever the visible window, sort, or filter changes. |
| `destroy` | No | Cleanup hook for releasing workers, sockets, or other resources. Called when the data source is detached from the grid. |
| `moveRow` | No | Called on row drag-end. Implement it to update the underlying data; the grid does not mutate data on its own. |
## Server Data Source
For server-side sorting, filtering, and windowed row loading:
```tsx
import { createServerDataSource } from "@gp-grid/react";
const dataSource = createServerDataSource(async (request) => {
const { range, sort, filter } = request;
const response = await fetch("/api/data", {
method: "POST",
body: JSON.stringify({
startRow: range.startRow,
endRow: range.endRow,
sortBy: sort,
filters: filter,
}),
});
const result = await response.json();
return {
rows: result.data,
totalRows: result.total,
};
});
```
Pass `{ loadMode: "all" }` as a second argument if the server returns the entire result set in one call. The default is `"paginated"` — the grid only requests the row window currently in view via `range.startRow` / `range.endRow`.
```tsx
const dataSource = createServerDataSource(queryFn, { loadMode: "paginated" });
```
### Request Interface
```tsx
interface DataSourceRequest {
range: {
startRow: number; // First row index to fetch (0-indexed, inclusive)
endRow: number; // First row index after the range (exclusive)
};
sort?: SortModel[];
filter?: FilterModel;
// Per-field formatters, used by client-side data sources for free-text
// conditions. Functions are not serializable — servers can ignore them.
valueFormatters?: Record string>;
}
```
Values-mode text filters arrive with `selectedValues` as a `Set` of **raw** cell values — a `valueFormatter` on the column never changes what the server receives. When forwarding the request as JSON, spread each Set into an array first: `JSON.stringify` turns a `Set` into `{}`.
### Response Interface
```tsx
interface DataSourceResponse {
rows: TData[];
totalRows: number;
}
```
## Choosing a Data Source
| Scenario | Recommended |
| ------------------------------------------------------------- | ------------------------------- |
| Small dataset (under 10k rows) | `rowData` prop |
| Large dataset, client-side | `createClientDataSource` |
| Editable data with CRUD, inside a React component | `useGridData` hook |
| Editable data, outside a component (module, shared singleton) | `createMutableClientDataSource` |
| Server-side operations | `createServerDataSource` |
# Events (React)
gp-grid uses an instruction-based architecture. The core emits declarative instructions that the React adapter interprets.
## Instruction Types
The grid emits various instructions during operation:
### Slot Instructions
```tsx
type CreateSlotInstruction = { type: "CREATE_SLOT"; slotId: string };
type DestroySlotInstruction = { type: "DESTROY_SLOT"; slotId: string };
type AssignSlotInstruction = { type: "ASSIGN_SLOT"; slotId: string; rowIndex: number; rowData: Row };
type MoveSlotInstruction = { type: "MOVE_SLOT"; slotId: string; translateY: number };
```
### Selection Instructions
```tsx
type SetActiveCellInstruction = { type: "SET_ACTIVE_CELL"; position: CellPosition | null };
type SetSelectionRangeInstruction = { type: "SET_SELECTION_RANGE"; range: CellRange | null };
type UpdateVisibleRangeInstruction = { type: "UPDATE_VISIBLE_RANGE"; start: number; end: number };
type SetHoverPositionInstruction = { type: "SET_HOVER_POSITION"; position: CellPosition | null };
```
### Editing Instructions
```tsx
type StartEditInstruction = { type: "START_EDIT"; row: number; col: number; initialValue: CellValue };
type StopEditInstruction = { type: "STOP_EDIT" };
type CommitEditInstruction = { type: "COMMIT_EDIT"; row: number; col: number; value: CellValue };
```
### Fill Handle Instructions
```tsx
type StartFillInstruction = { type: "START_FILL"; sourceRange: CellRange };
type UpdateFillInstruction = { type: "UPDATE_FILL"; targetRow: number; targetCol: number };
type CommitFillInstruction = { type: "COMMIT_FILL"; filledCells: Array<{ row: number; col: number; value: CellValue }> };
type CancelFillInstruction = { type: "CANCEL_FILL" };
```
### Column Move Instructions
```tsx
type StartColumnMoveInstruction = { type: "START_COLUMN_MOVE"; colIndex: number };
type UpdateColumnMoveInstruction = { type: "UPDATE_COLUMN_MOVE"; currentX: number; currentY: number; dropTargetIndex: number | null };
type CommitColumnMoveInstruction = { type: "COMMIT_COLUMN_MOVE"; sourceColIndex: number; targetColIndex: number };
type CancelColumnMoveInstruction = { type: "CANCEL_COLUMN_MOVE" };
```
### Row Drag Instructions
```tsx
type StartRowDragInstruction = { type: "START_ROW_DRAG"; rowIndex: number };
type UpdateRowDragInstruction = { type: "UPDATE_ROW_DRAG"; currentX: number; currentY: number; dropTargetIndex: number | null };
type CommitRowDragInstruction = { type: "COMMIT_ROW_DRAG"; sourceRowIndex: number; targetRowIndex: number };
type CancelRowDragInstruction = { type: "CANCEL_ROW_DRAG" };
```
### Column Resize Instructions
```tsx
type StartColumnResizeInstruction = { type: "START_COLUMN_RESIZE"; colIndex: number };
type UpdateColumnResizeInstruction = { type: "UPDATE_COLUMN_RESIZE"; colIndex: number; newWidth: number };
type CommitColumnResizeInstruction = { type: "COMMIT_COLUMN_RESIZE"; colIndex: number; newWidth: number };
type CancelColumnResizeInstruction = { type: "CANCEL_COLUMN_RESIZE" };
```
### Data Instructions
```tsx
type DataLoadingInstruction = { type: "DATA_LOADING" };
type DataLoadedInstruction = { type: "DATA_LOADED"; totalRows: number };
type DataErrorInstruction = { type: "DATA_ERROR"; error: string };
```
### Transaction Instructions
```tsx
type RowsAddedInstruction = { type: "ROWS_ADDED"; count: number; totalRows: number };
type RowsRemovedInstruction = { type: "ROWS_REMOVED"; count: number; totalRows: number };
type RowsUpdatedInstruction = { type: "ROWS_UPDATED"; count: number };
type TransactionProcessedInstruction = { type: "TRANSACTION_PROCESSED"; added: number; removed: number; updated: number };
```
### Header & Content Instructions
```tsx
type UpdateHeaderInstruction = { type: "UPDATE_HEADER"; headers: HeaderData[] };
type SetContentSizeInstruction = { type: "SET_CONTENT_SIZE"; width: number; height: number };
type ColumnsChangedInstruction = { type: "COLUMNS_CHANGED"; columns: ColumnDefinition[] };
```
### Scroll Instruction
```tsx
type ScrollToInstruction = { type: "SCROLL_TO"; row: number; col: number };
```
### Listener Types
```tsx
type InstructionListener = (instruction: GridInstruction) => void;
type BatchInstructionListener = (instructions: GridInstruction[]) => void;
```
You can subscribe to instructions directly on `GridCore` for advanced use cases:
```tsx
const unsubscribe = core.onBatchInstruction((instructions) => {
// Custom instruction handling
});
```
## Listening to Data Changes
With mutable data sources, subscribe to changes via the `subscribe` method:
```tsx
const { dataSource } = useGridData(data, {
getRowId: (row) => row.id,
});
const unsubscribe = dataSource.subscribe((result) => {
console.log("Data changed:", result);
// result: { added: number, removed: number, updated: number }
});
// Cleanup
unsubscribe();
```
Or listen with the raw factory using `onTransactionProcessed`:
```tsx
const dataSource = createMutableClientDataSource(seed, {
getRowId: (row) => row.id,
onTransactionProcessed: (result) => {
console.log(`Added: ${result.added}, Removed: ${result.removed}, Updated: ${result.updated}`);
},
});
```
## Position Types
```tsx
interface CellPosition {
row: number;
col: number;
}
interface CellRange {
startRow: number;
startCol: number;
endRow: number;
endCol: number;
}
```
# Grid Props (React)
The `GridProps` interface defines all properties accepted by the Grid component.
```tsx
interface GridProps {
columns: ColumnDefinition[];
dataSource?: DataSource;
rowData?: TData[];
rowHeight: number;
headerHeight?: number;
overscan?: number;
sortingEnabled?: boolean;
darkMode?: boolean;
wheelDampening?: number;
/** Initial viewport width for SSR (pixels). ResizeObserver takes over on client. */
initialWidth?: number;
/** Initial viewport height for SSR (pixels). ResizeObserver takes over on client. */
initialHeight?: number;
cellRenderers?: Record;
editRenderers?: Record;
headerRenderers?: Record;
cellRenderer?: ReactCellRenderer;
editRenderer?: ReactEditRenderer;
headerRenderer?: ReactHeaderRenderer;
rowDragEntireRow?: boolean;
/** Function to extract unique ID from row. Required when onCellValueChanged is provided. */
getRowId?: (row: TData) => RowId;
/** Called when a cell value is changed via editing or fill drag. Requires getRowId. */
onCellValueChanged?: (event: CellValueChangedEvent) => void;
onRowDragEnd?: (sourceIndex: number, targetIndex: number) => void;
onColumnMoved?: (fromIndex: number, toIndex: number) => void;
onColumnResized?: (colIndex: number, newWidth: number) => void;
/** Row/column/cell highlighting configuration. See Styling guide for usage. */
highlighting?: HighlightingOptions;
/** Custom loading component to render instead of default spinner. */
loadingComponent?: React.ComponentType<{ isLoading: boolean }>;
/** Optional ref to access GridCore API */
gridRef?: React.MutableRefObject | null>;
}
```
## Required Props
### columns
**Type:** `ColumnDefinition[]`
Array of column definitions. See [Column Definition](/docs/react/api/column-definition) for details.
```tsx
const columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80 },
{ field: "name", cellDataType: "text", width: 200 },
];
```
### rowHeight
**Type:** `number`
Height of each row in pixels.
```tsx
```
## Data Props
### rowData
**Type:** `TData[]`
Array of row data objects. This is the simplest way to provide data.
```tsx
const data = [
{ id: 1, name: "Giovanni" },
{ id: 2, name: "Luca" },
];
```
### dataSource
**Type:** `DataSource`
Data source for advanced data handling. Use this for server-side data or when you need mutation capabilities. See [Data Sources](/docs/react/api/data-sources).
```tsx
const dataSource = createClientDataSource(data);
```
## Optional Props
### headerHeight
**Type:** `number`
**Default:** Same as `rowHeight`
Height of the header row in pixels.
```tsx
```
### overscan
**Type:** `number`
**Default:** `3`
Number of rows to render outside the visible viewport. Higher values improve scroll smoothness at the cost of more DOM elements.
```tsx
```
### sortingEnabled
**Type:** `boolean`
**Default:** `true`
Enable or disable sorting globally.
```tsx
```
### darkMode
**Type:** `boolean`
**Default:** `false`
Enable dark mode styling.
```tsx
```
### wheelDampening
**Type:** `number`
**Default:** `0.1`
Dampening factor for wheel scrolling (0-1). Lower values = smoother but slower scrolling.
Enabling this prop activates **smooth scrolling** for grids that exceed millions of rows by using virtual dampening, which keeps wheel-based navigation fluid on very large datasets.
```tsx
```
### maxFlingVelocity
**Type:** `number`
**Default:** `20 × rowHeight`
Maximum accumulated touch-fling velocity in logical px/ms when scroll virtualization is active. Pair higher values with `overscan` 10–12 for smoother fast scrolling on touch devices.
```tsx
```
## Drag & Reorder Props
### rowDragEntireRow
**Type:** `boolean`
**Default:** `false`
When enabled, clicking and dragging any cell in a row initiates a row drag instead of cell selection.
```tsx
dataSource.moveRow(from, to)}
/>
```
### onRowDragEnd
**Type:** `(sourceIndex: number, targetIndex: number) => void`
Called when a row is dropped after dragging. The consumer is responsible for reordering the data.
```tsx
{
dataSource.moveRow(sourceIndex, targetIndex);
}}
/>
```
### onColumnMoved
**Type:** `(fromIndex: number, toIndex: number) => void`
Called when a column header is dragged to a new position. The consumer is responsible for reordering the columns array.
```tsx
{
setColumns((prev) => {
const next = [...prev];
const [moved] = next.splice(fromIndex, 1);
next.splice(toIndex, 0, moved);
return next;
});
}}
/>
```
### onColumnResized
**Type:** `(colIndex: number, newWidth: number) => void`
Called when a column is resized by dragging the header edge.
```tsx
{
console.log(`Column ${colIndex} resized to ${newWidth}px`);
}}
/>
```
## SSR Props
### initialWidth
**Type:** `number`
Initial viewport width in pixels for server-side rendering. On the client, a `ResizeObserver` takes over automatically.
### initialHeight
**Type:** `number`
Initial viewport height in pixels for server-side rendering.
## Identity & Edit Props
### getRowId
**Type:** `(row: TData) => RowId`
Function to extract a unique ID from each row. **Required** when `onCellValueChanged` is provided.
```tsx
row.id}
onCellValueChanged={(event) => console.log("Changed:", event)}
/>
```
### onCellValueChanged
**Type:** `(event: CellValueChangedEvent) => void`
Called after a cell value is committed via inline editing or fill-handle drag. Requires `getRowId` to be set.
```tsx
row.id}
onCellValueChanged={({ rowId, colIndex, oldValue, newValue }) => {
console.log(`Row ${rowId} col ${colIndex}: ${oldValue} → ${newValue}`);
}}
/>
```
## Styling Props
### highlighting
**Type:** `HighlightingOptions`
Dynamic row, column, and cell class computation. See the [Styling guide](/docs/react/guides/styling) for detailed usage.
```tsx
context.rowIndex != null && context.rowIndex % 2 === 1
? ["zebra-row"]
: [],
computeCellClasses: (context) =>
context.isSelected ? ["bg-blue-50"] : [],
}}
/>
```
## Loading Props
### loadingComponent
**Type:** `React.ComponentType<{ isLoading: boolean }>`
Custom component rendered during data loading instead of the default spinner.
```tsx
function MySpinner({ isLoading }: { isLoading: boolean }) {
return isLoading ?
Loading…
: null;
}
```
## Ref Props
### gridRef
**Type:** `React.MutableRefObject | null>`
Ref handle exposing the underlying `GridCore` instance for imperative API access.
```tsx
const gridRef = useRef>(null);
// Later:
gridRef.current?.core?.setSort("name", "asc");
gridRef.current?.core?.startEdit(0, 1);
```
## Renderer Props
### cellRenderer
**Type:** `ReactCellRenderer`
Global cell renderer applied to all cells.
```tsx
const MyCellRenderer = (params: CellRendererParams) => (
{params.value?.toString()}
);
```
### cellRenderers
**Type:** `Record`
Registry of named cell renderers. Reference by key in column definitions.
```tsx
```
### editRenderer / editRenderers
**Type:** `ReactEditRenderer` / `Record`
Custom edit renderers for cells in edit mode.
### headerRenderer / headerRenderers
**Type:** `ReactHeaderRenderer` / `Record`
Custom header renderers.
# API Overview (React)
Complete API documentation for @gp-grid/react.
All props accepted by the Grid component
Configure individual columns
Client-side and server-side data handling
Grid instructions and events
## Exports
The `@gp-grid/react` package exports the following:
```tsx
// Component
import { Grid } from "@gp-grid/react";
// Types
import type {
GridProps,
GridRef,
ColumnDefinition,
CellDataType,
CellValue,
CellPosition,
CellRange,
SortDirection,
SortModel,
FilterModel,
DataSource,
DataSourceRequest,
DataSourceResponse,
MutableDataSource,
CellRendererParams,
EditRendererParams,
HeaderRendererParams,
ReactCellRenderer,
ReactEditRenderer,
ReactHeaderRenderer,
GridInstruction,
UseGridDataOptions,
UseGridDataResult,
} from "@gp-grid/react";
// Data Source Factories
import {
createClientDataSource,
createDataSourceFromArray,
createMutableClientDataSource,
createServerDataSource,
} from "@gp-grid/react";
// Hooks
import { useGridData } from "@gp-grid/react";
```
# Column Dragging (React)
gp-grid supports column reordering by dragging column headers. This allows users to customize the column layout to their preference.
## Interactive Demo
Drag any column header to reorder it. The "ID" column has `movable: false` and stays in place.
## Basic Usage
Column dragging is enabled by default. Simply provide an `onColumnMoved` callback to handle the reordering:
```tsx title="ColumnDraggingGrid.tsx" {14,32}
import { useState, useCallback } from "react";
import { Grid, type ColumnDefinition } from "@gp-grid/react";
const initialColumns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
{ field: "email", cellDataType: "text", width: 250, headerName: "Email" },
{ field: "role", cellDataType: "text", width: 120, headerName: "Role" },
];
function ColumnDraggingGrid() {
const [columns, setColumns] = useState(initialColumns);
const handleColumnMoved = useCallback(
(fromIndex: number, toIndex: number) => {
setColumns((prev) => {
const next = [...prev];
const [moved] = next.splice(fromIndex, 1);
next.splice(toIndex, 0, moved);
return next;
});
},
[]
);
return (
);
}
```
## Disable Column Dragging
### Per Column
Set `movable: false` on individual columns to prevent them from being dragged:
```tsx
const columns: ColumnDefinition[] = [
// [!code highlight]
{ field: "id", cellDataType: "number", width: 80, movable: false },
{ field: "name", cellDataType: "text", width: 150 },
{ field: "email", cellDataType: "text", width: 250 },
];
```
## How It Works
1. Click and hold a column header
2. Drag it to the desired position — a visual indicator shows the drop target
3. Release to complete the reorder
4. The `onColumnMoved` callback fires with `fromIndex` and `toIndex`
5. Your code reorders the columns array to reflect the new order
## Key Points
* Column dragging is enabled by default (`movable: true`)
* The consumer is responsible for reordering the columns array in `onColumnMoved`
* A ghost element follows the cursor during the drag
* The drop target is highlighted to show where the column will land
* Dragging uses a threshold to avoid triggering on simple clicks (which would sort)
# Custom Renderers (React)
Customize how cells and headers are rendered with custom renderer functions.
## Interactive Demo
This demo shows three custom renderers in action:
* **Status** - Colored dot indicator (green/amber/gray)
* **Performance** - Positive values in green, negative in red
* **Salary** - Formatted as currency
Free-text filter conditions and copy-paste operate on the **formatted value** — the string users actually see — while the values (checkbox) filter and sorting work on **raw** values. A `cellRenderer` only changes how a cell is *painted*; it does not affect filtering at all. Pair every custom renderer with a matching `valueFormatter` so the text users type into a filter matches what they see:
```tsx
{
field: "salary",
cellDataType: "number",
width: 140,
cellRenderer: SalaryRenderer,
valueFormatter: (value) => `$${(value as number).toLocaleString()}`,
}
```
Without the formatter, users typing `"$82,000"` into the Salary column's text condition would match nothing because the comparison would run against the raw `82000`. See the full [`valueFormatter` reference](/docs/react/api/column-definition#valueformatter) for signature, caveats, and more examples.
## Cell Renderers
### Global Cell Renderer
Apply a renderer to all cells:
```tsx {16}
import { Grid, type CellRendererParams } from "@gp-grid/react";
const MyCellRenderer = (params: CellRendererParams) => {
return (
{params.value?.toString()}
);
};
```
### Per-Column Renderers (Inline)
Pass a renderer function directly in the column definition:
```tsx
const StatusRenderer = (params: CellRendererParams) => {
const status = params.value as string;
const color = status === "Active" ? "green" : "red";
return (
{status}
);
};
const columns: ColumnDefinition[] = [
{ field: "name", cellDataType: "text", width: 150 },
{
field: "status",
cellDataType: "text",
width: 120,
// [!code highlight]
cellRenderer: StatusRenderer,
},
];
```
### Per-Column Renderers (Registry)
Alternatively, register renderers by key and reference them by name. This is useful when the same renderer is shared across multiple columns:
```tsx {7,16}
const columns: ColumnDefinition[] = [
{ field: "name", cellDataType: "text", width: 150 },
{
field: "status",
cellDataType: "text",
width: 120,
cellRenderer: "status"
},
];
```
## Header Renderers
Customize column headers:
```tsx {18}
import { type HeaderRendererParams } from "@gp-grid/react";
const MyHeaderRenderer = (params: HeaderRendererParams) => {
return (
);
}
```
## Key Concepts
### Custom Cell Renderer
The "Change" column uses a custom renderer to display positive values in green and negative values in red:
```tsx
import { type CellRendererParams } from "@gp-grid/react";
const ChangeRenderer = (params: CellRendererParams) => {
const value = params.value as number;
const isPositive = value >= 0;
return (
{isPositive ? "+" : ""}
{value.toFixed(2)}
);
};
// Reference the renderer in the column definition
const columns: ColumnDefinition[] = [
// ...other columns
{
field: "change",
cellDataType: "number",
width: 100,
headerName: "Change",
// [!code highlight]
cellRenderer: "change",
},
];
// Pass the renderer to the Grid
```
### Subscribing to Data Changes
Use `dataSource.subscribe` to react to data mutations (adds, removes, updates):
```tsx
const { dataSource } = useGridData(data, {
getRowId: (row) => row.id,
debounceMs: 50,
});
useEffect(() => {
// [!code highlight]
const unsubscribe = dataSource.subscribe(() => {
// Fires after each batch of operations is processed
const totalRows = dataSource.getTotalRowCount();
setRowCount(totalRows);
});
return () => unsubscribe();
}, [dataSource]);
```
### Debouncing for Performance
The `debounceMs` option batches rapid operations:
```tsx
// With debounceMs: 50, these three calls become one transaction
dataSource.addRows([tick1]);
dataSource.addRows([tick2]);
dataSource.addRows([tick3]);
// Grid updates once after 50ms with all 3 rows
```
### Streaming Data
For continuous data streams, use `setInterval` with `addRows`:
```tsx
const { addRows } = useGridData(data, { getRowId: (row) => row.id });
const intervalId = setInterval(() => {
addRows([generateNewData()]);
}, 100); // Add new row every 100ms
// Cleanup
clearInterval(intervalId);
```
### Flushing Transactions
Before operations that depend on current state, flush pending transactions:
```tsx
const { dataSource, removeRows } = useGridData(data, {
getRowId: (row) => row.id,
});
async function removeAllRows() {
// Ensure all pending adds are processed first
await dataSource.flushTransactions();
const count = dataSource.getTotalRowCount();
const ids = getAllRowIds();
removeRows(ids);
}
```
## Performance Tips
1. **Adjust debounce for your use case** - Lower values (10-50ms) for real-time feel, higher values (100-500ms) for better batching.
2. **Use batch operations** - `addRows([row1, row2, row3])` is more efficient than three separate `addRows` calls.
3. **Leverage subscriptions** - Update derived state in `dataSource.subscribe` rather than polling.
4. **Clean up intervals** - Always clear streaming intervals when components unmount:
```tsx
useEffect(() => {
return () => {
if (streamingRef.current) {
clearInterval(streamingRef.current);
}
};
}, []);
```
# Row Dragging (React)
gp-grid supports row reordering via drag and drop. You can either designate a column as a drag handle or allow dragging from any cell.
## Interactive Demo
Toggle between drag handle mode and entire row mode. Drag rows to reorder them.
## Drag Handle Column
Set `rowDrag: true` on a column to make it act as a drag handle:
```tsx title="RowDraggingGrid.tsx" {9,33}
import { useCallback } from "react";
import {
Grid,
useGridData,
type ColumnDefinition,
} from "@gp-grid/react";
const columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 40, rowDrag: true, sortable: false },
{ field: "task", cellDataType: "text", width: 200, headerName: "Task" },
{ field: "assignee", cellDataType: "text", width: 120, headerName: "Assignee" },
{ field: "status", cellDataType: "text", width: 100, headerName: "Status" },
];
function RowDraggingGrid() {
const { dataSource } = useGridData(initialData, {
getRowId: (row) => row.id,
});
const handleRowDragEnd = useCallback(
(sourceIndex: number, targetIndex: number) => {
dataSource.moveRow(sourceIndex, targetIndex);
},
[dataSource]
);
return (
);
}
```
## Entire Row Dragging
Set `rowDragEntireRow={true}` on the Grid to allow dragging from any cell:
```tsx {5}
```
When `rowDragEntireRow` is enabled, clicking and dragging any cell initiates a row drag instead of a cell selection.
## Data Reordering
The consumer is responsible for reordering data after a drag. Call `dataSource.moveRow()` from the `useGridData` hook:
```tsx
const { dataSource } = useGridData(data, {
getRowId: (row) => row.id,
});
const handleRowDragEnd = (sourceIndex: number, targetIndex: number) => {
dataSource.moveRow(sourceIndex, targetIndex);
};
```
## How It Works
1. Click and hold a drag handle (or any cell if `rowDragEntireRow` is enabled)
2. Drag up or down — a drop indicator line shows the target position
3. Release to drop the row
4. `onRowDragEnd` fires with `sourceIndex` and `targetIndex`
5. Call `dataSource.moveRow()` to reorder the data
## Key Points
* Use `rowDrag: true` on a column for a dedicated drag handle
* Use `rowDragEntireRow={true}` on the Grid to drag from any cell
* The drop indicator works correctly with virtual scrolling, even with millions of rows
* Always use a mutable data source via `useGridData` for row reordering
* The `onRowDragEnd` callback provides display indices — the data source handles the mapping
# Selection (React)
gp-grid supports cell selection with range and multi-select capabilities.
## Single Cell Selection
Click any cell to select it. The active cell is highlighted with a distinct border.
## Range Selection
Click and drag to select a range of cells:
1. Click a cell to start
2. Drag to extend the selection
3. Release to complete the range
Or use keyboard:
1. Click a cell
2. Hold `Shift`
3. Click another cell to select the range
## Keyboard Navigation
| Key | Action |
| --------------- | ---------------------------- |
| `Arrow keys` | Move active cell |
| `Shift + Arrow` | Extend selection |
| `Tab` | Move to next cell |
| `Shift + Tab` | Move to previous cell |
| `Enter` | Move down (or start editing) |
| `Home` | Go to first cell in row |
| `End` | Go to last cell in row |
| `Ctrl + Home` | Go to first cell |
| `Ctrl + End` | Go to last cell |
## Selection Styling
Selected cells receive distinct styling:
* **Active cell**: Bold border
* **Selected range**: Highlighted background
The styling automatically adapts to dark mode when `darkMode={true}`.
# Sorting (React)
gp-grid supports single and multi-column sorting out of the box.
## Basic Sorting
Sorting is enabled by default. Click a column header to sort:
```tsx title="SortableGrid.tsx" {16}
import { Grid, type ColumnDefinition } from "@gp-grid/react";
const columns: ColumnDefinition[] = [
{ field: "id", cellDataType: "number", width: 80, headerName: "ID" },
{ field: "name", cellDataType: "text", width: 150, headerName: "Name" },
{ field: "salary", cellDataType: "number", width: 120, headerName: "Salary" },
];
function SortableGrid() {
return (
);
}
```
## Multi-Column Sorting
Hold `Shift` while clicking headers to sort by multiple columns:
1. Click "Name" to sort by name
2. `Shift+Click` "Salary" to add salary as secondary sort
## Disable Sorting
### Globally
```tsx
```
### Per Column
```tsx
const columns: ColumnDefinition[] = [
// [!code highlight]
{ field: "id", cellDataType: "number", width: 80, sortable: false },
{ field: "name", cellDataType: "text", width: 150, sortable: true },
];
```
## Sort Indicators
The header displays sort direction and order:
* `▲` - Ascending
* `▼` - Descending
* Number badge for multi-column sort order
# Guides Overview (React)
In-depth guides for common use cases and best practices.
Tips for handling large datasets efficiently
Customize the grid appearance
Integrate with server-side sorting, filtering, and pagination
# Performance (React)
gp-grid is designed for high performance with large datasets. Here are tips to maximize performance.
## Virtual Scrolling
gp-grid uses slot-based virtual scrolling. Instead of rendering all rows, it maintains a pool of DOM elements (slots) that are recycled as you scroll. This keeps DOM node count constant regardless of data size.
### How It Works
1. Only visible rows plus overscan are rendered
2. As you scroll, slots are reassigned to new rows
3. DOM elements are moved via CSS transforms, not recreated
This means 1.5 million rows perform the same as 100 rows.
## Web Workers for Sorting
For large datasets, sorting is automatically offloaded to a Web Worker:
```tsx
const dataSource = createClientDataSource(data, {
useWorker: true // Default: true
});
```
This keeps the main thread responsive during sort operations.
## Memory Considerations
### Row Data Structure
Keep row objects lean. Avoid:
```tsx
// Bad: nested objects with redundant data
const rows = data.map(item => ({
...item,
computed: expensiveComputation(item),
references: { parent: item.parent, children: item.children },
}));
```
```tsx
// Good: flat, minimal structure
const rows = data.map(item => ({
id: item.id,
name: item.name,
value: item.value,
}));
```
### Memoize Row Data
Avoid recreating row arrays on every render:
```tsx
// Bad: creates new array each render
function MyGrid() {
const rows = data.map(transform);
return ;
}
```
```tsx
// Good: memoized
function MyGrid() {
const rows = useMemo(() => data.map(transform), [data]);
return ;
}
```
## Overscan Tuning
The `overscan` prop controls how many extra rows render outside the viewport:
```tsx
// Fewer DOM nodes, may see flicker on fast scroll
// More DOM nodes, smoother fast scrolling
// Default: 3
```
## Custom Renderers
Keep renderers simple and avoid heavy computations:
```tsx
// Bad: expensive operations in renderer
const SlowRenderer = (params: CellRendererParams) => {
const formatted = expensiveFormat(params.value);
const validated = complexValidation(params.rowData);
return
;
};
```
Pre-compute derived values in your data if needed.
## Server-Side Operations
For truly massive datasets (10M+ rows), consider server-side data:
```tsx
const dataSource = createServerDataSource(async (request) => {
// Sorting, filtering, pagination handled by server
return fetchFromAPI(request);
});
```
This keeps client memory usage minimal since only the visible page is loaded.
## Smooth Scrolling for Million-Row Grids
Enable virtual dampening to keep wheel scrolling smooth on grids that exceed millions of rows:
```tsx
```
The `wheelDampening` prop accepts a value between `0` and `1`. Lower values produce smoother scrolling but travel less distance per wheel event, which is ideal when precise navigation matters more than raw speed.
For touch devices, pair `wheelDampening` with `maxFlingVelocity` to control scroll momentum after a flick gesture:
```tsx
```
The default `maxFlingVelocity` is `20 × rowHeight`. Raise it together with `overscan` (10–12 recommended) to make fast touch-flicking feel more responsive.
## Benchmarks
On a typical modern laptop:
| Rows | Initial Render | Scroll FPS |
| --------- | -------------- | ---------- |
| 1,000 | \~10ms | 60fps |
| 100,000 | \~50ms | 60fps |
| 1,000,000 | \~100ms | 60fps |
| 5,000,000 | \~200ms | 60fps |
Scroll performance is consistent regardless of dataset size due to virtual scrolling.
# Server-Side Data (React)
For very large datasets or when data must remain on the server, use server-side data operations.
## When to Use Server-Side
* Dataset too large to load into browser memory
* Data requires real-time updates from server
* Complex filtering/sorting logic on server
* Security requirements to keep data server-side
## Basic Setup
```tsx
import { Grid, createServerDataSource } from "@gp-grid/react";
const dataSource = createServerDataSource(async (request) => {
const response = await fetch("/api/grid-data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
return response.json();
});
function ServerGrid() {
return (
);
}
```
## Request Format
The server receives a request describing the absolute row range, plus sort/filter state. `endRow` is exclusive, so `endRow - startRow` is the number of rows to return.
```tsx
interface DataSourceRequest {
range: {
startRow: number; // First row index to fetch (0-indexed, inclusive)
endRow: number; // First row index after the range (exclusive)
};
sort?: SortModel[]; // Sort configuration
filter?: FilterModel; // Filter configuration
}
// Example request — the grid asks for rows 0..99 sorted by name asc
{
range: { startRow: 0, endRow: 100 },
sort: [{ colId: "name", direction: "asc" }],
filter: {
salary: {
conditions: [{ type: "number", operator: ">", value: 50000 }],
combination: "and"
}
}
}
```
## Response Format
Return rows and total count:
```tsx
interface DataSourceResponse {
rows: TData[]; // Current page of data
totalRows: number; // Total rows matching filters
}
// Example response
{
rows: [
{ id: 1, name: "Giovanni", salary: 75000 },
{ id: 2, name: "Luca", salary: 82000 },
// ...
],
totalRows: 10000
}
```
## Express.js Example
```ts
// server.ts
import express from "express";
app.post("/api/grid-data", async (req, res) => {
const { range, sort, filter } = req.body;
// Build database query
let query = db.select().from(employees);
// Apply filters
if (filter) {
for (const [field, model] of Object.entries(filter)) {
for (const condition of model.conditions) {
if (condition.type === "number") {
switch (condition.operator) {
case ">":
query = query.where(field, ">", condition.value);
break;
case "<":
query = query.where(field, "<", condition.value);
break;
// ... other operators
}
}
// ... handle other types
}
}
}
// Apply sorting
if (sort?.length) {
for (const s of sort) {
query = query.orderBy(s.colId, s.direction);
}
}
// Get total count
const totalRows = await query.clone().count();
// Apply range (endRow is exclusive)
const { startRow, endRow } = range;
query = query.offset(startRow).limit(endRow - startRow);
const rows = await query;
res.json({ rows, totalRows });
});
```
## Handling Sort Models
```tsx
// SortModel structure
type SortModel = {
colId: string;
direction: "asc" | "desc";
};
// Multiple columns (for multi-sort)
[
{ colId: "department", direction: "asc" },
{ colId: "salary", direction: "desc" }
]
```
## Handling Filter Models
```tsx
// FilterModel structure
type FilterModel = Record;
// ColumnFilterModel
{
conditions: FilterCondition[];
combination: "and" | "or";
}
// Example: salary > 50000 AND salary < 100000
{
salary: {
conditions: [
{ type: "number", operator: ">", value: 50000 },
{ type: "number", operator: "<", value: 100000 }
],
combination: "and"
}
}
// Example: values (checkbox) filter — selectedValues holds RAW cell values
{
status: {
conditions: [
{
type: "text",
operator: "equals",
selectedValues: new Set([0, 1]), // raw values, never display labels
includeBlank: false // true when the "(Blanks)" entry is ticked
}
],
combination: "and"
}
}
```
`selectedValues` always contains **raw** cell values: a `valueFormatter` on the column changes only what the popup displays, never what your server receives. Match them directly against your database column (e.g. `WHERE status IN (...)`), and treat `includeBlank: true` as "also include NULL/empty rows".
`selectedValues` is a `Set`, and `JSON.stringify` serializes a `Set` as `{}`. Convert it before sending the request over the wire:
```tsx
const serializeFilter = (filter: FilterModel) =>
Object.fromEntries(
Object.entries(filter).map(([field, model]) => [
field,
{
...model,
conditions: model.conditions.map((c) =>
"selectedValues" in c && c.selectedValues
? { ...c, selectedValues: [...c.selectedValues] }
: c,
),
},
]),
);
```
## Error Handling
```tsx
const dataSource = createServerDataSource(async (request) => {
try {
const response = await fetch("/api/grid-data", {
method: "POST",
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
} catch (error) {
console.error("Failed to fetch grid data:", error);
// Return empty result on error
return { rows: [], totalRows: 0 };
}
});
```
## Caching Considerations
For better UX, consider caching pages:
```tsx
const cache = new Map();
const dataSource = createServerDataSource(async (request) => {
const cacheKey = JSON.stringify(request);
if (cache.has(cacheKey)) {
return cache.get(cacheKey)!;
}
const result = await fetchFromServer(request);
cache.set(cacheKey, result);
return result;
});
```
Clear cache when data changes on the server.
# Styling (React)
gp-grid includes built-in styles with dark mode support. You can customize the appearance using CSS.
## Dark Mode
Enable dark mode with the `darkMode` prop:
```tsx
```
### With next-themes
```tsx
"use client";
import { useTheme } from "next-themes";
function ThemedGrid() {
const { resolvedTheme } = useTheme();
return (
);
}
```
## CSS Classes
The grid uses these CSS classes for styling:
| Class | Description |
| ------------------------- | ---------------------- |
| `.gp-grid` | Grid container |
| `.gp-grid-header` | Header row |
| `.gp-grid-header-cell` | Individual header cell |
| `.gp-grid-body` | Body container |
| `.gp-grid-row` | Row container |
| `.gp-grid-cell` | Individual cell |
| `.gp-grid-cell--active` | Active cell |
| `.gp-grid-cell--selected` | Selected cell |
| `.gp-grid-cell--editing` | Cell in edit mode |
## Zero-Specificity Defaults
All of the grid's *visual* defaults (colors, borders, cursors, padding) are declared inside [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where), which has zero specificity. Only structural properties (positioning, sizing, overflow) use plain selectors. This means any class selector you write wins over the built-in look — no `!important`, no specificity battles:
```css
/* This wins over the built-in :where(.gp-grid-cell) defaults */
.gp-grid-cell {
color: #334155;
}
```
## Custom Styling with CSS
Override styles in your global CSS:
```css
/* Custom header styling */
.gp-grid-header {
background: linear-gradient(to right, #667eea, #764ba2);
}
.gp-grid-header-cell {
color: white;
font-weight: 600;
}
/* Custom selection colors */
.gp-grid-cell--active {
outline: 2px solid #3b82f6;
}
.gp-grid-cell--selected {
background-color: rgba(59, 130, 246, 0.1);
}
/* Alternating row colors */
.gp-grid-row:nth-child(even) {
background-color: rgba(0, 0, 0, 0.02);
}
/* Dark mode adjustments */
.dark .gp-grid-row:nth-child(even) {
background-color: rgba(255, 255, 255, 0.02);
}
```
### Disabling the Selection Highlight and Cell Cursor
By default the grid highlights the active cell with a border and background, and shows the excel-like `cell` cursor. Thanks to the zero-specificity defaults, plain class selectors are enough to turn both off:
```css
/* Turn off the active (focused) cell highlight */
.gp-grid-cell--active {
background-color: transparent;
border: none;
padding: 0 12px; /* the default reserves 11px next to its 2px border */
}
/* Turn off the range-selection highlight */
.gp-grid-cell--selected {
background-color: transparent;
}
/* Normal arrow cursor instead of the excel-like cell cursor */
.gp-grid-cell {
cursor: default;
}
```
The fill handle keeps its own `crosshair` cursor via `.gp-grid-fill-handle`; override that class too if you want it gone.
## Tailwind CSS
With Tailwind, use `@apply` in your CSS:
```css
@layer components {
.gp-grid-header {
@apply bg-gradient-to-r from-purple-500 to-pink-500;
}
.gp-grid-header-cell {
@apply text-white font-semibold;
}
.gp-grid-cell--active {
@apply ring-2 ring-blue-500;
}
}
```
## Dynamic Class Computation
Use the `highlighting` prop to dynamically compute CSS classes for rows, columns, or cells based on data or interaction state.
### highlighting Prop
```tsx
interface HighlightingOptions {
computeRowClasses?: (context: HighlightContext) => string[];
computeColumnClasses?: (context: HighlightContext) => string[];
computeCellClasses?: (context: HighlightContext) => string[];
}
```
### Row Classes
Apply classes to entire rows based on row data or hover state:
```tsx
{
const classes: string[] = [];
// Zebra striping
if (context.rowIndex !== null && context.rowIndex % 2 === 1) {
classes.push("zebra-row");
}
// Hover effect
if (context.isHovered) {
classes.push("row-highlight");
}
return classes;
},
}}
/>
```
### Column Classes
Apply classes to entire columns:
```tsx
context.isHovered ? ["column-highlight"] : [],
}}
/>
```
### Cell Classes
Apply classes to individual cells:
```tsx
{
if (context.isHovered) {
return ["cell-highlight"];
}
return [];
},
}}
/>
```
### HighlightContext
All callbacks receive a context object with these properties:
| Property | Type | Description |
| ------------ | --------------------- | -------------------------------------------- |
| `rowIndex` | `number \| null` | Row index (null for column-only context) |
| `colIndex` | `number \| null` | Column index (null for row-only context) |
| `column` | `HighlightColumnInfo` | Column definition (for column/cell contexts) |
| `rowData` | `TData` | Row data (for row/cell contexts) |
| `isHovered` | `boolean` | Whether this row/column/cell is hovered |
| `isActive` | `boolean` | Whether this contains the active cell |
| `isSelected` | `boolean` | Whether this is in the selection range |
For more examples, see the [Highlighting Example](/docs/react/examples/highlighting).
## Custom Cell Styling via Renderers
For dynamic styling based on data, use custom renderers:
```tsx
const ConditionalRenderer = (params: CellRendererParams) => {
const value = params.value as number;
const color = value >= 100000 ? "text-green-600" : "text-red-600";
return (
${value.toLocaleString()}
);
};
```
## Container Styling
Style the grid container for borders, shadows, etc.:
```tsx
```
The `overflow-hidden` ensures rounded corners are visible.
# Column Definition (Vue)
The `ColumnDefinition` interface is shared with the React package.
```ts
interface ColumnDefinition {
field: string;
colId?: string;
cellDataType: CellDataType;
width: number;
headerName?: string;
editable?: boolean;
sortable?: boolean;
filterable?: boolean;
hidden?: boolean;
resizable?: boolean;
minWidth?: number;
maxWidth?: number;
movable?: boolean;
rowDrag?: boolean;
cellRenderer?: string | ((params: CellRendererParams) => unknown);
editRenderer?: string | ((params: EditRendererParams) => unknown);
headerRenderer?: string | ((params: HeaderRendererParams) => unknown);
valueFormatter?: (value: CellValue) => string;
distinctValues?: CellValue[];
computeColumnClasses?: (context: HighlightContext) => string[];
computeCellClasses?: (context: HighlightContext) => string[];
}
```
## Required Properties
### field
**Type:** `string`
Property name in row data.
### cellDataType
**Type:** `CellDataType`
| Value | Description |
| ------------------ | -------------------- |
| `"text"` | String values |
| `"number"` | Numeric values |
| `"boolean"` | Boolean values |
| `"date"` | Date objects |
| `"dateString"` | ISO date strings |
| `"dateTime"` | DateTime objects |
| `"dateTimeString"` | ISO DateTime strings |
| `"object"` | Complex objects |
### width
**Type:** `number`
Column width in pixels.
## Optional Properties
### colId
**Type:** `string`
**Default:** Same as `field`
### headerName
**Type:** `string`
**Default:** Same as `field`
### editable
**Type:** `boolean`
**Default:** `false`
### sortable
**Type:** `boolean`
**Default:** `true`
### filterable
**Type:** `boolean`
**Default:** `true`
### hidden
**Type:** `boolean`
**Default:** `false`
Whether the column is hidden.
### resizable
**Type:** `boolean`
**Default:** `true`
Whether the column can be resized by dragging the header edge.
### minWidth / maxWidth
**Type:** `number`
**Default:** `50` / `undefined`
Minimum and maximum width constraints when resizing.
### movable
**Type:** `boolean`
**Default:** `true`
Whether the column can be reordered by dragging its header.
### rowDrag
**Type:** `boolean`
**Default:** `false`
Whether this column acts as a drag handle for row dragging.
### cellRenderer / editRenderer / headerRenderer
**Type:** `string | ((params) => unknown)`
Pass a render function directly or a string key to look up in the renderer registry.
```vue
```
### valueFormatter
**Type:** `(value: CellValue) => string`
Converts a raw cell value into the string the grid treats as the cell's **display value**. The formatter runs before the default cell renderer paints the cell, so the output is what the user sees on screen.
Beyond display, each feature uses the formatter in a specific way:
* **Values (checkbox) filter mode** — the popup groups its checkbox entries by formatted label; ticking a label selects every raw value that renders as that label. The filter model itself — and any request sent to a server data source — always carries the **raw** values, never the labels.
* **Free-text filter conditions** (`contains`, `equals`, ...) — compared against the formatted string, so users can type exactly what they see in the cell.
* **Copy to clipboard** — copies the formatted string.
* **Sorting** — compares **raw** values; the formatter is not involved.
```vue
```
The signature takes the raw value directly (`(value) => ...`), not a params object. Inside a `cellRenderer` the `params.value` you receive is already the *formatted* string when a `valueFormatter` is set — read `params.rowData[column.field]` if the renderer needs the raw value.
#### Pairing with `cellRenderer`
A `cellRenderer` only changes how a cell is *painted*; it does not change the value used by filters or sort. Pair custom renderers with a matching `valueFormatter` so both stay in sync:
```vue
```
Without the formatter, a user typing `"$82,000"` into a free-text condition would match nothing because the comparison would run against the raw `82000`.
#### Formatters that collapse values
Several raw values may format to the same label (e.g. dates formatted to their month). The values filter shows one checkbox per label, and ticking it selects all raw values behind it — but only the raw values the grid has discovered. On high-cardinality columns, pre-supply the full raw domain via `distinctValues` so no raw value is missed:
```ts
{
field: "priority",
cellDataType: "number",
width: 120,
valueFormatter: (value) => (value as number) > 1 ? "High" : "Low",
distinctValues: [0, 1, 2, 3], // full raw domain, raw values only
}
```
The grid warns in the console when its distinct-value scan is cut short for a formatted column.
#### Common use cases
Formatting `"object"`-type columns where the default `JSON.stringify` is unhelpful:
```ts
{
field: "address",
cellDataType: "object",
width: 250,
valueFormatter: (value) => {
const addr = value as { city: string; country: string };
return `${addr.city}, ${addr.country}`;
},
}
```
Date display with a library-free format:
```ts
{
field: "createdAt",
cellDataType: "dateString",
width: 140,
valueFormatter: (value) => new Date(value as string).toLocaleDateString(),
}
```
Null-safe formatting:
```ts
valueFormatter: (value) => value == null ? "—" : String(value),
```
### computeColumnClasses
**Type:** `(context: HighlightContext) => string[]`
Per-column override for column-level highlighting. Overrides the grid-level `computeColumnClasses` for this column. The context has `colIndex` set and `rowIndex` is `null`.
```ts
{
field: "status",
cellDataType: "text",
width: 120,
computeColumnClasses: (context) =>
context.colIndex === 0 ? ["first-column"] : [],
}
```
### computeCellClasses
**Type:** `(context: HighlightContext) => string[]`
Per-column override for cell-level highlighting. Overrides the grid-level `computeCellClasses` for cells in this column. The context has both `rowIndex` and `colIndex` set.
```ts
{
field: "score",
cellDataType: "number",
width: 100,
computeCellClasses: (context) => {
const value = context.rowData?.score as number;
if (value != null && value < 60) return ["bg-red-50"];
if (value != null && value >= 90) return ["bg-green-50"];
return [];
},
}
```
For full context shape, see the [Styling guide](/docs/vue/guides/styling).
## Example
```vue
```
# Data Sources (Vue)
gp-grid provides data source abstractions shared with the React package.
## Basic Usage
```vue
```
## Client Data Source
```vue
```
## Mutable Data Source
For CRUD operations, use the **`useGridData` composable** inside `
```
Reach for the raw `createMutableClientDataSource` factory only outside components — module-level singletons, composables that need their own lifecycle, or shared utilities. Inside a component, prefer `useGridData`: it handles subscription cleanup on unmount and keeps the dataSource reference stable across re-renders.
```ts
// Outside a component (e.g. a shared module)
import { createMutableClientDataSource } from "@gp-grid/vue";
export const sharedDataSource = createMutableClientDataSource(seed, {
getRowId: (row) => row.id,
});
```
### useGridData Options
```ts
interface UseGridDataOptions {
getRowId: (row: TData) => RowId; // Required: unique ID accessor
debounceMs?: number; // Batch window (default: 50)
useWorker?: boolean; // Use Web Worker for sorting (default: true)
parallelSort?: ParallelSortOptions | false;
}
```
### MutableDataSource Methods
```ts
interface MutableDataSource extends DataSource {
addRows(rows: TData[]): void;
removeRows(ids: RowId[]): void;
updateCell(id: RowId, field: string, value: CellValue): void;
updateRow(id: RowId, data: Partial): void;
flushTransactions(): Promise;
hasPendingTransactions(): boolean;
getDistinctValues(field: string): CellValue[];
getRowById(id: RowId): TData | undefined;
getTotalRowCount(): number;
subscribe(listener: DataChangeListener): () => void;
clear(): void;
moveRow(fromIndex: number, toIndex: number): void;
}
```
## DataSource Interface
All data sources implement the `DataSource` interface. Both `createClientDataSource` and `createServerDataSource` return objects that conform to it; `MutableDataSource` extends it with mutation methods.
```ts
interface DataSource {
readonly loadMode?: "all" | "paginated";
query(request: DataSourceRequest): Promise>;
destroy?(): void;
moveRow?(fromIndex: number, toIndex: number): void;
}
```
| Member | Required | Description |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `loadMode` | No | `"all"` (default for client data sources) or `"paginated"` (default for server). Controls whether the grid requests every row at once or only the visible window via `range.startRow` / `range.endRow`. |
| `query` | Yes | Async function returning `{ rows, totalRows }` for the requested range, sort, and filter. Called by the grid whenever the visible window, sort, or filter changes. |
| `destroy` | No | Cleanup hook for releasing workers, sockets, or other resources. Called when the data source is detached from the grid. |
| `moveRow` | No | Called on row drag-end. Implement it to update the underlying data; the grid does not mutate data on its own. |
## Server Data Source
```vue
```
Pass `{ loadMode: "all" }` as a second argument if the server returns the entire result set in one call. The default is `"paginated"` — the grid only requests the row window currently in view via `range.startRow` / `range.endRow`.
```ts
const dataSource = createServerDataSource(queryFn, { loadMode: "paginated" });
```
### Request / Response
```ts
interface DataSourceRequest {
range: {
startRow: number; // First row index to fetch (0-indexed, inclusive)
endRow: number; // First row index after the range (exclusive)
};
sort?: SortModel[];
filter?: FilterModel;
// Per-field formatters, used by client-side data sources for free-text
// conditions. Functions are not serializable — servers can ignore them.
valueFormatters?: Record string>;
}
interface DataSourceResponse {
rows: TData[];
totalRows: number;
}
```
Values-mode text filters arrive with `selectedValues` as a `Set` of **raw** cell values — a `valueFormatter` on the column never changes what the server receives. When forwarding the request as JSON, spread each Set into an array first: `JSON.stringify` turns a `Set` into `{}`.
## With Nuxt useFetch
```vue
```
## Reactive Updates
Data sources work with Vue's reactivity:
```vue
```
# Grid Props (Vue)
All properties accepted by the Vue Grid component. Props use kebab-case in templates.
## Required Props
### columns
**Type:** `ColumnDefinition[]`
Array of column definitions.
```vue
```
### row-height
**Type:** `number`
Height of each row in pixels.
```vue
```
## Data Props
### row-data
**Type:** `TData[]`
Array of row data objects.
```vue
```
### data-source
**Type:** `DataSource`
Data source for advanced data handling.
```vue
```
## Optional Props
### header-height
**Type:** `number`
**Default:** Same as `row-height`
```vue
```
### overscan
**Type:** `number`
**Default:** `3`
Number of rows to render outside viewport.
### sorting-enabled
**Type:** `boolean`
**Default:** `true`
Enable/disable sorting globally.
### dark-mode
**Type:** `boolean`
**Default:** `false`
Enable dark mode styling.
### wheel-dampening
**Type:** `number`
**Default:** `0.1`
Dampening factor for wheel scrolling (0-1).
Enabling this prop activates **smooth scrolling** for grids that exceed millions of rows by using virtual dampening, which keeps wheel-based navigation fluid on very large datasets.
### max-fling-velocity
**Type:** `number`
**Default:** `20 × rowHeight`
Maximum accumulated touch-fling velocity in logical px/ms when scroll virtualization is active. Pair higher values with `overscan` 10–12 for smoother fast scrolling on touch devices.
```vue
```
## Drag & Reorder Props
### row-drag-entire-row
**Type:** `boolean`
**Default:** `false`
When enabled, clicking and dragging any cell initiates a row drag.
```vue
```
### @row-drag-end
**Type:** `(sourceIndex: number, targetIndex: number) => void`
Emitted when a row is dropped after dragging. The consumer handles data reordering.
### @column-moved
**Type:** `(fromIndex: number, toIndex: number) => void`
Emitted when a column header is dragged to a new position.
### @column-resized
**Type:** `(colIndex: number, newWidth: number) => void`
Emitted when a column is resized by dragging the header edge.
## SSR Props
### initial-width
**Type:** `number`
Initial viewport width in pixels for server-side rendering. On the client, a `ResizeObserver` takes over automatically.
### initial-height
**Type:** `number`
Initial viewport height in pixels for server-side rendering.
## Identity & Edit Props
### get-row-id
**Type:** `(row: TData) => RowId`
Function to extract a unique ID from each row. Required when `onCellValueChanged` is provided.
```vue
```
### @cell-value-changed
**Type:** `(event: CellValueChangedEvent) => void`
Emitted after a cell value is committed via inline editing or fill-handle drag.
## Styling Props
### highlighting
**Type:** `HighlightingOptions`
Dynamic row, column, and cell class computation. See the [Styling guide](/docs/vue/guides/styling) for detailed usage.
```vue
```
## Loading Props
### loading-component
**Type:** `Component<{ isLoading: boolean }>`
Custom component rendered during data loading instead of the default spinner.
## Renderer Props
### cell-renderer
**Type:** `VueCellRenderer`
Global cell renderer.
### cell-renderers
**Type:** `Record`
Named cell renderer registry.
### edit-renderer / edit-renderers
Custom edit renderers.
### header-renderer / header-renderers
Custom header renderers.
## Template Ref
You can access the underlying `GridCore` instance via a template ref:
```vue
```
## Example
```vue
```
# API Overview (Vue)
Complete API documentation for @gp-grid/vue.
All props accepted by the Grid component
Configure individual columns
Client-side and server-side data handling
## Exports
```ts
// Component
import { GpGrid } from "@gp-grid/vue";
// or
import Grid from "@gp-grid/vue";
// Types
import type {
GpGridProps,
ColumnDefinition,
CellDataType,
CellValue,
CellPosition,
CellRange,
SortDirection,
SortModel,
FilterModel,
DataSource,
DataSourceRequest,
DataSourceResponse,
MutableDataSource,
CellRendererParams,
EditRendererParams,
HeaderRendererParams,
VueCellRenderer,
VueEditRenderer,
VueHeaderRenderer,
UseGridDataOptions,
UseGridDataResult,
} from "@gp-grid/vue";
// Data Source Factories
import {
createClientDataSource,
createDataSourceFromArray,
createMutableClientDataSource,
createServerDataSource,
} from "@gp-grid/vue";
// Composables
import {
useGridData,
useGpGrid,
useInputHandler,
useAutoScroll,
useFillHandle,
useFilterPopup,
useFilterConditions,
useGridState,
} from "@gp-grid/vue";
```
# Column Dragging (Vue)
gp-grid supports column reordering by dragging column headers.
## Interactive Demo
## Basic Usage
Column dragging is enabled by default. Provide an `onColumnMoved` handler to update the column order:
```vue {14,28}
```
## Disable Column Dragging
### Per Column
Set `movable: false` on individual columns:
```vue
```
## How It Works
1. Click and hold a column header
2. Drag it to the desired position
3. Release to complete the reorder
4. The `column-moved` event fires with `fromIndex` and `toIndex`
5. Update the columns array to reflect the new order
## Key Points
* Column dragging is enabled by default (`movable: true`)
* The consumer is responsible for reordering the columns array
* Set `movable: false` per column to prevent dragging
* Dragging uses a threshold to avoid triggering on clicks
# Custom Renderers (Vue)
Customize how cells and headers are rendered.
## Interactive Demo
## Cell Renderers
gp-grid for Vue accepts two renderer styles and you can mix them freely in the same grid:
* **`h()` render functions** — great for small inline renderers
* **Single-File Components** — great for anything with template syntax, scoped styles, or reusable logic
### Mixing both in one grid
```vue title="StatusCell.vue"
{{ props.value }}
```
```vue title="PeopleGrid.vue" {4,21,28-29}
```
Both styles receive the same `CellRendererParams` and participate in the same virtualization pool — there is no performance penalty for mixing them.
Free-text filter conditions and copy-paste operate on the **formatted value** — the string users actually see — while the values (checkbox) filter and sorting work on **raw** values. A `cellRenderer` only changes how a cell is *painted*; it does not affect filtering at all. Pair every custom renderer with a matching `valueFormatter` so the text users type into a filter matches what they see:
```ts
{
field: "salary",
cellDataType: "number",
width: 140,
cellRenderer: SalaryRenderer,
valueFormatter: (value) => `$${(value as number).toLocaleString()}`,
}
```
Without the formatter, users typing `"$82,000"` into the Salary column's text condition would match nothing because the comparison would run against the raw `82000`. See the full [`valueFormatter` reference](/docs/vue/api/column-definition#valueformatter) for signature, caveats, and more examples.
### Registry shorthand
When the same renderer is used across multiple columns, register it once on the grid and reference it by key:
```vue {8,15}
```
The value of the registry map can itself be either an SFC or an `h()` function.
## Header Renderers
Headers accept the same two styles as cells — `h()` functions or SFCs. The snippet below uses `h()` for brevity:
```vue {22}
```
## Renderer Parameters
### CellRendererParams
| Property | Type | Description |
| ------------ | ------------------ | --------------------- |
| `value` | `CellValue` | Current cell value |
| `rowData` | `TData` | Full row data object |
| `column` | `ColumnDefinition` | Column definition |
| `rowIndex` | `number` | Row index |
| `colIndex` | `number` | Column index |
| `isActive` | `boolean` | Is the active cell |
| `isSelected` | `boolean` | Is in selection range |
| `isEditing` | `boolean` | Is being edited |
### HeaderRendererParams
| Property | Type | Description |
| --------------- | ---------------------------- | -------------------- |
| `column` | `ColumnDefinition` | Column definition |
| `colIndex` | `number` | Column index |
| `sortDirection` | `SortDirection \| undefined` | Current sort |
| `sortIndex` | `number \| undefined` | Multi-sort order |
| `sortable` | `boolean` | Is column sortable |
| `filterable` | `boolean` | Is column filterable |
| `hasFilter` | `boolean` | Has active filter |
| `onSort` | `function` | Trigger sort |
| `onFilterClick` | `function` | Open filter popup |
# Editing (Vue)
gp-grid supports inline cell editing with Excel-like fill handle.
## Interactive Demo
## Enable Editing
Set `editable: true` on column definitions:
```vue
```
## Start Editing
| Action | Description |
| ------------ | -------------------------------- |
| Double-click | Open editor with current value |
| `Enter` | Open editor with current value |
| `F2` | Open editor with current value |
| Start typing | Open editor with typed character |
## Commit or Cancel
| Action | Description |
| ------------- | ----------------------------- |
| `Enter` | Commit changes and move down |
| `Tab` | Commit changes and move right |
| `Escape` | Cancel changes |
| Click outside | Commit changes |
## Fill Handle
The fill handle appears at the bottom-right corner of selected cells. Drag it to fill values.
## Custom Edit Renderers
```vue {29}
```
# Filtering (Vue)
gp-grid provides powerful column filtering.
## Basic Filtering
Click the filter icon in a column header to open the filter popup:
```vue
```
## Filter Operators
### Text Filters
| Operator | Description |
| ------------- | --------------------------- |
| `contains` | Value contains text |
| `notContains` | Value does not contain text |
| `equals` | Exact match |
| `notEquals` | Not an exact match |
| `startsWith` | Value starts with text |
| `endsWith` | Value ends with text |
| `blank` | Value is empty |
| `notBlank` | Value is not empty |
### Number Filters
| Operator | Description |
| --------- | --------------------- |
| `=` | Equal to |
| `!=` | Not equal to |
| `>` | Greater than |
| `<` | Less than |
| `>=` | Greater than or equal |
| `<=` | Less than or equal |
| `between` | Within range |
## Values Mode
Text filter popups also offer a checkbox list of the column's distinct values. Entries are grouped by display label: with a `valueFormatter`, several raw values can share one label, and ticking it selects all of them. The applied filter always stores the **raw** values (`selectedValues: Set`), so a formatter never changes what a server data source receives.
When the column contains blank cells — `null`, empty strings, or empty arrays such as a tags column with no tags — the list shows a **(Blanks)** entry. Untick it to exclude blank rows from the result.
## Disable Filtering
```vue
```
# Highlighting (Vue)
This example demonstrates the highlighting API using `computeRowClasses`, `computeColumnClasses`, and `computeCellClasses` to create dynamic hover effects.
## Interactive Demo
## Overview
The highlighting API provides three callback functions that let you dynamically apply CSS classes to rows, columns, or cells based on various contexts including hover state.
## Highlighting Modes
### Row Hover
Highlight the entire row when any cell in that row is hovered:
```vue {5,13}
```
### Column Highlight
Highlight the entire column when any cell in that column is hovered:
```vue
```
### Crosshairs Effect
Combine row and column highlighting for a crosshair effect:
```vue
```
### Cell Hover
Highlight only the individual cell being hovered:
```vue
```
## API Reference
### `highlighting` Prop
The `highlighting` prop accepts an object with three optional callback functions:
```ts
interface HighlightingOptions {
computeRowClasses?: (context: HighlightContext) => string[];
computeColumnClasses?: (context: HighlightContext) => string[];
computeCellClasses?: (context: HighlightContext) => string[];
}
```
### HighlightContext
All three callbacks receive the same unified context type. The `rowIndex` and `colIndex` fields indicate the context type:
* **Row context**: `rowIndex` is set, `colIndex` is null
* **Column context**: `colIndex` is set, `rowIndex` is null
* **Cell context**: both `rowIndex` and `colIndex` are set
```ts
interface HighlightContext {
/** Row index. Null for column-only context. */
rowIndex: number | null;
/** Column index. Null for row-only context. */
colIndex: number | null;
/** Column definition. Present for column and cell contexts. */
column?: HighlightColumnInfo;
/** Row data. Present for row and cell contexts. */
rowData?: TData;
/** Currently hovered cell position */
hoverPosition: CellPosition | null;
/** Currently active (focused) cell position */
activeCell: CellPosition | null;
/** Current selection range */
selectionRange: CellRange | null;
/** Whether this row/column/cell is hovered */
isHovered: boolean;
/** Whether this row/column contains the active cell */
isActive: boolean;
/** Whether this row/column/cell is in the selection range */
isSelected: boolean;
}
```
## CSS Classes
Add these CSS classes to your stylesheet for the highlighting effects:
```css
/* Row highlighting */
.row-highlight {
background-color: rgba(147, 51, 234, 0.1);
}
.dark .row-highlight {
background-color: rgba(168, 85, 247, 0.15);
}
/* Column highlighting */
.column-highlight {
background-color: rgba(59, 130, 246, 0.1);
}
.dark .column-highlight {
background-color: rgba(96, 165, 250, 0.15);
}
/* Cell highlighting */
.cell-highlight {
background-color: rgba(34, 197, 94, 0.2);
}
.dark .cell-highlight {
background-color: rgba(74, 222, 128, 0.25);
}
```
## Zebra Striping Example
You can use `computeRowClasses` for zebra striping without any hover logic:
```vue
```
```css
.zebra-row {
background-color: rgba(0, 0, 0, 0.02);
}
.dark .zebra-row {
background-color: #1a1b1e;
}
```
## Conditional Highlighting
You can combine multiple conditions for more complex highlighting logic:
```vue
```
## Performance Notes
* Callback functions are called during render, so keep them lightweight
* Return the same array reference when possible to avoid unnecessary re-renders
* Consider memoizing complex class computations if performance is a concern
# Examples (Vue)
Explore code examples demonstrating gp-grid's features with Vue.
# Row Dragging (Vue)
gp-grid supports row reordering via drag and drop. You can designate a column as a drag handle or allow dragging from any cell.
## Interactive Demo
## Drag Handle Column
Set `rowDrag: true` on a column to make it act as a drag handle:
```vue {5,15,26}
```
## Entire Row Dragging
Set `row-drag-entire-row` to allow dragging from any cell:
```vue
```
## Data Reordering
The consumer is responsible for reordering data. Call `dataSource.moveRow()` from the `useGridData` composable:
```vue
```
## Key Points
* Use `rowDrag: true` on a column for a dedicated drag handle
* Use `:row-drag-entire-row="true"` to drag from any cell
* Always use a mutable data source via `useGridData` for row reordering
* The drop indicator works correctly with virtual scrolling
# Selection (Vue)
gp-grid supports cell selection with range and multi-select capabilities.
## Single Cell Selection
Click any cell to select it.
## Range Selection
Click and drag to select a range of cells, or:
1. Click a cell
2. Hold `Shift`
3. Click another cell
## Keyboard Navigation
| Key | Action |
| --------------- | ---------------------------- |
| `Arrow keys` | Move active cell |
| `Shift + Arrow` | Extend selection |
| `Tab` | Move to next cell |
| `Shift + Tab` | Move to previous cell |
| `Enter` | Move down (or start editing) |
| `Home` | Go to first cell in row |
| `End` | Go to last cell in row |
| `Ctrl + Home` | Go to first cell |
| `Ctrl + End` | Go to last cell |
## Selection Styling
Selected cells receive distinct styling that adapts to dark mode.
# Sorting (Vue)
gp-grid supports single and multi-column sorting.
## Basic Sorting
Sorting is enabled by default. Click a column header to sort:
```vue title="SortableGrid.vue" {19}
```
## Multi-Column Sorting
Hold `Shift` while clicking headers to sort by multiple columns.
## Disable Sorting
### Globally
```vue
```
### Per Column
```vue
```
## Sort Indicators
The header displays sort direction:
* `▲` - Ascending
* `▼` - Descending
* Number badge for multi-column sort order
# Guides Overview (Vue)
In-depth guides for Vue-specific use cases.
Tips for handling large datasets efficiently
Customize the grid appearance
# Performance (Vue)
Performance tips specific to Vue 3.
## Avoid Reactive Overhead
For large datasets, use `shallowRef` instead of `ref`:
```vue
```
## Memoize Computed Columns
```vue
```
## Use Web Workers
The client data source uses Web Workers for sorting by default:
```vue
```
## Virtual Scrolling
gp-grid handles this automatically. Performance is constant regardless of data size.
## Smooth Scrolling for Million-Row Grids
Enable virtual dampening to keep wheel scrolling smooth on grids that exceed millions of rows:
```vue
```
The `wheel-dampening` prop accepts a value between `0` and `1`. Lower values produce smoother scrolling but travel less distance per wheel event, which is ideal when precise navigation matters more than raw speed.
For touch devices, pair `wheel-dampening` with `max-fling-velocity` to control scroll momentum after a flick gesture:
```vue
```
The default `max-fling-velocity` is `20 × rowHeight`. Raise it together with `overscan` (10–12 recommended) to make fast touch-flicking feel more responsive.
## Server-Side for Massive Data
For very large datasets, use server-side data:
```vue
```
# Styling (Vue)
Customize grid appearance in Vue applications.
## Dark Mode
```vue
```
### With Nuxt Color Mode
```vue
```
## CSS Customization
All of the grid's *visual* defaults (colors, borders, cursors, padding) are declared inside [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where), which has zero specificity, so any class selector you write wins over the built-in look — no `!important` needed:
```css
/* In your global styles */
.gp-grid-header {
background: linear-gradient(to right, #667eea, #764ba2);
}
.gp-grid-cell--active {
outline: 2px solid #3b82f6;
}
.gp-grid-row:nth-child(even) {
background-color: rgba(0, 0, 0, 0.02);
}
```
### Disabling the Selection Highlight and Cell Cursor
By default the grid highlights the active cell with a border and background, and shows the excel-like `cell` cursor. Plain class selectors turn both off:
```css
/* Turn off the active (focused) cell highlight */
.gp-grid-cell--active {
background-color: transparent;
border: none;
padding: 0 12px; /* the default reserves 11px next to its 2px border */
}
/* Turn off the range-selection highlight */
.gp-grid-cell--selected {
background-color: transparent;
}
/* Normal arrow cursor instead of the excel-like cell cursor */
.gp-grid-cell {
cursor: default;
}
```
The fill handle keeps its own `crosshair` cursor via `.gp-grid-fill-handle`; override that class too if you want it gone.
## Scoped Styles
Use `:deep()` for scoped style overrides:
```vue
```
## Dynamic Class Computation
Use the `highlighting` prop to dynamically compute CSS classes for rows, columns, or cells based on data or interaction state.
### highlighting Prop
```ts
interface HighlightingOptions {
computeRowClasses?: (context: HighlightContext) => string[];
computeColumnClasses?: (context: HighlightContext) => string[];
computeCellClasses?: (context: HighlightContext) => string[];
}
```
### Row Classes
Apply classes to entire rows based on row data or hover state:
```vue
```
### Column Classes
Apply classes to entire columns:
```vue
```
### Cell Classes
Apply classes to individual cells:
```vue
```
### HighlightContext
All callbacks receive a context object with these properties:
| Property | Type | Description |
| ------------ | --------------------- | -------------------------------------------- |
| `rowIndex` | `number \| null` | Row index (null for column-only context) |
| `colIndex` | `number \| null` | Column index (null for row-only context) |
| `column` | `HighlightColumnInfo` | Column definition (for column/cell contexts) |
| `rowData` | `TData` | Row data (for row/cell contexts) |
| `isHovered` | `boolean` | Whether this row/column/cell is hovered |
| `isActive` | `boolean` | Whether this contains the active cell |
| `isSelected` | `boolean` | Whether this is in the selection range |
For more examples, see the [Highlighting Example](/docs/vue/examples/highlighting).
## Container Styling
```vue