# CoreUI Data Grid React.js documentation > High-performance React data grid for CoreUI — 100,000 rows with sorting, filtering, selection and pagination. --- # CoreUI React Data Grid > High-performance React data grid — 100,000 rows with sorting, filtering, selection, pagination, server-side data, column resizing, pinning, ordering and full theming. CoreUI Data Grid is a high-performance grid for displaying and interacting with large tabular datasets. It renders 100,000 rows in the browser with sorting, filtering, selection and pagination, and hands off to your API when the data outgrows the browser's memory. ## Why Data Grid - **Fast by default.** Row [virtualization](https://coreui.io/data-grid/react/docs/features/virtualization/) keeps only the visible window in the DOM, so scrolling stays smooth at 100k rows. - **Headless core, styled shell.** A proven headless table engine and row virtualization under a CoreUI-themed UI. Drop to the [headless table](https://coreui.io/data-grid/react/docs/api/headless/) any time. - **Complete feature set.** Sorting, [filtering](https://coreui.io/data-grid/react/docs/features/filtering/), [selection](https://coreui.io/data-grid/react/docs/features/row-selection/), [pagination](https://coreui.io/data-grid/react/docs/features/pagination/), [server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/), column [sizing](https://coreui.io/data-grid/react/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/react/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/), a [column menu](https://coreui.io/data-grid/react/docs/columns/menu/) and [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/). - **Themeable.** Every knob is a `--cui-data-grid-*` CSS variable resolving through CoreUI semantic tokens, so light/dark theming works with no extra CSS. See [Styling & theming](https://coreui.io/data-grid/react/docs/customization/styling/). - **Small.** Around 48 KB gzipped for the underlying grid core. ## How it's built `` is a thin, styled layer over a headless table engine (state, sorting, filtering, pagination, pinning, ordering, visibility) and row virtualization (windowed rendering). Props are named after the features they control, callbacks are verbs (`onSortingChange`, `onSelectionChange`, …), and their payloads carry the grid's own state — a small, predictable API. ```tsx import { CDataGrid } from '@coreui/react-data-grid' import '@coreui/data-grid/dist/css/data-grid.css' ``` ## Get started 1. [Install](https://coreui.io/data-grid/react/docs/getting-started/installation/) the package. 2. Follow the [Quickstart](https://coreui.io/data-grid/react/docs/getting-started/quickstart/) to render your first grid. 3. Browse the [feature matrix](https://coreui.io/data-grid/react/docs/getting-started/features/) to see what's available. ## Packages | Package | Framework | Docs | | ------- | --------- | ---- | | `@coreui/data-grid` | Vanilla JavaScript | [coreui.io/data-grid/docs](https://coreui.io/data-grid/docs/) | | `@coreui/react-data-grid` | React | [coreui.io/data-grid/react/docs](https://coreui.io/data-grid/react/docs/) | | `@coreui/vue-data-grid` | Vue | [coreui.io/data-grid/vue/docs](https://coreui.io/data-grid/vue/docs/) | | `@coreui/angular-data-grid` | Angular | [coreui.io/data-grid/angular/docs](https://coreui.io/data-grid/angular/docs/) | --- # React Data Grid Installation > Install CoreUI Data Grid for React via npm and load its stylesheet. ## npm ```sh npm install @coreui/react-data-grid ``` Data Grid ships its stylesheet in the `@coreui/data-grid` package (installed automatically as a dependency) and layers on the `.table` styles from `@coreui/coreui` or `@coreui/coreui-pro` (≥ 5), so make sure one of them is loaded on the page. ```tsx import { CDataGrid } from '@coreui/react-data-grid' import '@coreui/data-grid/dist/css/data-grid.css' ``` ## Stylesheet The grid's CSS defines `--cui-data-grid-*` custom properties that resolve through CoreUI's semantic variables, so it inherits your theme (including `data-coreui-theme="dark"`) automatically. See [Styling & theming](https://coreui.io/data-grid/react/docs/customization/styling/) for the full token reference. Next: the [Quickstart](https://coreui.io/data-grid/react/docs/getting-started/quickstart/). --- # React Data Grid Quickstart > Render your first CoreUI Data Grid for React in a few lines — columns, data, a stable row key, then your first feature. This guide builds a working grid from scratch. It assumes you've [installed](https://coreui.io/data-grid/react/docs/getting-started/installation/) `@coreui/react-data-grid` and loaded its stylesheet. ## 1. The component The grid renders wherever you place ``: ```tsx import { CDataGrid } from '@coreui/react-data-grid' import '@coreui/data-grid/dist/css/data-grid.css' ``` ## 2. Columns and data Define columns by `key` (the property to read from each item) and pass your `items`: ```tsx const items = [ { id: 1, name: 'Alice', role: 'admin' }, { id: 2, name: 'Bob', role: 'editor' }, { id: 3, name: 'Carol', role: 'viewer' }, ] String(item.id)} /> ``` `itemKey` returns a stable id per row. It's optional, but [selection](https://coreui.io/data-grid/react/docs/features/row-selection/) needs it to survive sorting and filtering — set it up front. ## 3. Turn on a feature Every feature is a single prop. Add filtering and selection: ```tsx String(item.id)} columnFilters // per-column filter row rowSelection // checkbox column with select-all /> ``` Sorting is on by default. From here, explore the [feature matrix](https://coreui.io/data-grid/react/docs/getting-started/features/) or jump to any feature page. ## 4. React to changes The grid calls `onXxx` [callback props](https://coreui.io/data-grid/react/docs/api/events/) with structured state: ```tsx String(item.id)} rowSelection onSelectionChange={(rowSelection, selectedItems) => { console.log(selectedItems) }} /> ``` ## What's next - Handle large or remote data with [server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/). - Customize cells with a column [`formatter` or `render`](https://coreui.io/data-grid/react/docs/columns/overview/). - Replace built-in chrome with [slots](https://coreui.io/data-grid/react/docs/features/slots/) or drive the [headless table](https://coreui.io/data-grid/react/docs/api/headless/) directly. --- # React Data Grid Features > A capability matrix of everything CoreUI Data Grid for React does today, with the prop that turns each feature on and a link to its docs. Everything the Data Grid does today, the prop that enables it, and where to read more. Features not listed here are on the [roadmap](https://coreui.io/data-grid/react/docs/resources/roadmap/). ## Data & rendering | Feature | Prop | Docs | | --- | --- | --- | | Row virtualization | `virtualization` (on by default) | [Virtualization](https://coreui.io/data-grid/react/docs/features/virtualization/) | | Pagination | `pagination` | [Pagination](https://coreui.io/data-grid/react/docs/features/pagination/) | | Server-side data | `dataProvider` | [Server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/) | | Row selection | `rowSelection` | [Row selection](https://coreui.io/data-grid/react/docs/features/row-selection/) | ## Sorting & filtering | Feature | Prop | Docs | | --- | --- | --- | | Column sorting (multi-column) | `sorting` (on by default) | [Sorting](https://coreui.io/data-grid/react/docs/features/sorting/) | | Per-column filter row | `columnFilters` | [Filtering](https://coreui.io/data-grid/react/docs/features/filtering/) | | Global search | `globalFilter` | [Filtering](https://coreui.io/data-grid/react/docs/features/filtering/) | | Custom filter UI / predicate | `filter`, `filterFn` (per column) | [Filtering](https://coreui.io/data-grid/react/docs/features/filtering/) | ## Columns | Feature | Prop | Docs | | --- | --- | --- | | Custom cell formatting / rendering | `formatter`, `render` (per column) | [Columns overview](https://coreui.io/data-grid/react/docs/columns/overview/) | | Column resizing | `columnSizing` | [Column sizing](https://coreui.io/data-grid/react/docs/columns/sizing/) | | Column pinning | `columnPinning` | [Column pinning](https://coreui.io/data-grid/react/docs/columns/pinning/) | | Column ordering (drag & drop) | `columnOrder` | [Ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) | | Column visibility | `columnVisibility` | [Ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) | | Column header menu | `columnMenu` | [Column menu](https://coreui.io/data-grid/react/docs/columns/menu/) | ## Customization & output | Feature | Prop / API | Docs | | --- | --- | --- | | Custom toolbar / pagination / empty state | `slots` | [Slots](https://coreui.io/data-grid/react/docs/features/slots/) | | CSV export | `exportCsv()`, `downloadCsv()` | [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/) | | Theming (CSS variables) | `--cui-data-grid-*` | [Styling & theming](https://coreui.io/data-grid/react/docs/customization/styling/) | | Localization (i18n) | `labels` | [Localization](https://coreui.io/data-grid/react/docs/customization/localization/) | | Headless escape hatch | `tableRef` | [Headless table](https://coreui.io/data-grid/react/docs/api/headless/) | --- # LLMs.txt > LLM-optimized documentation endpoints for CoreUI React Data Grid — llms.txt, llms-full.txt, and a Markdown version of every page. ## Introduction [llms.txt](https://llmstxt.org) is an emerging standard that helps AI models understand and navigate documentation. The CoreUI React Data Grid docs expose three LLM-friendly endpoints so assistants can retrieve accurate, up-to-date content straight from the source. For a richer, tool-based integration, see [MCP Server](https://coreui.io/data-grid/react/docs/ai-tools/mcp/). ## /llms.txt A structured index of the documentation — every page as a titled, described link, grouped by section. It gives an LLM a compact map of what exists and where. [Open llms.txt](https://coreui.io/data-grid/react/docs/llms.txt) ## /llms-full.txt The entire documentation concatenated into a single Markdown file, so a model can ingest the whole set in one request. [Open llms-full.txt](https://coreui.io/data-grid/react/docs/llms-full.txt) ## Markdown version of any page Append `.md` to any documentation page URL to get its clean Markdown version, without the site chrome. For example: [/data-grid/react/docs/features/sorting.md](https://coreui.io/data-grid/react/docs/features/sorting.md) --- # MCP Server > Bring the CoreUI React Data Grid documentation into your AI coding assistant with the @coreui/docs-mcp Model Context Protocol server. ## Introduction [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that lets AI assistants connect to external tools and data sources. The **`@coreui/docs-mcp`** server gives your assistant direct access to the official CoreUI documentation, so it answers from the current docs instead of relying on stale training data. Point it at the CoreUI React Data Grid docs with the `--base-path` option shown below. It provides: - **Documentation pages** — getting started, features, columns, and API reference. - **Live content** — read on demand from `coreui.io`, always matching the latest release. - **Structured API** — options, events, and methods for the grid. The server runs locally over stdio via `npx` — no global install required. ## Installation ### Claude Code Add the server with the CLI, then start a new session and run `/mcp` to verify the connection: ```bash claude mcp add coreui-data-grid -s user -- npx -y @coreui/docs-mcp --framework react --base-path /data-grid/react/docs ``` ### Cursor Create `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` for global configuration): ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "react", "--base-path", "/data-grid/react/docs"] } } } ``` ### VS Code Create `.vscode/mcp.json` in your project. Note that VS Code uses the `servers` key: ```json { "servers": { "coreui-data-grid": { "type": "stdio", "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "react", "--base-path", "/data-grid/react/docs"] } } } ``` ### Windsurf Edit `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "react", "--base-path", "/data-grid/react/docs"] } } } ``` ### Claude Desktop Edit `claude_desktop_config.json` (Settings → Developer → Edit Config): ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "react", "--base-path", "/data-grid/react/docs"] } } } ``` ### OpenAI Codex Add it with the CLI, or edit `~/.codex/config.toml` directly: ```bash codex mcp add coreui-data-grid -- npx -y @coreui/docs-mcp --framework react --base-path /data-grid/react/docs ``` ```toml [mcp_servers.coreui-data-grid] command = "npx" args = ["-y", "@coreui/docs-mcp", "--framework", "react", "--base-path", "/data-grid/react/docs"] ``` ## Tools Once connected, your assistant can call the following tools: | Tool | Description | | --- | --- | | `list_components` | List documentation pages, optionally filtered by section or a substring. | | `search_docs` | Search the documentation and return the best matching pages. | | `get_doc_page` | Fetch the full Markdown of a page by slug or URL. | | `get_component_api` | Get the structured API (options, events, methods) for the grid. | ## Configuration | Flag | Environment variable | Default | Description | | --- | --- | --- | --- | | `--framework ` | `COREUI_DOCS_FRAMEWORKS` | `bootstrap,react,vue` | Enabled editions (comma-separated). The first is the default for tools. | | `--base-url ` | `COREUI_DOCS_BASE_URL` | `https://coreui.io` | Origin of the CoreUI site. Override only for a staging or self-hosted mirror. | | `--base-path ` | `COREUI_DOCS_BASE_PATH` | `/{framework}/docs` | Path after the origin where the docs live; `{framework}` is substituted. | | `--docs-path ` | `COREUI_DOCS_PATHS` | — | Per-framework path overrides, `fw=path` comma-separated. | | `--ttl ` | `COREUI_DOCS_TTL_MINUTES` | `360` | Cache freshness window. | | — | `COREUI_DOCS_CACHE_DIR` | OS cache directory | On-disk cache location. | ## Example prompts Once installed, try asking your AI assistant: - "How do I enable row selection in CoreUI React Data Grid?" - "What options does CoreUI React Data Grid accept?" - "Show me the CoreUI React Data Grid pagination documentation." - "How do I export CoreUI React Data Grid data to CSV?" The package is open source and published as [`@coreui/docs-mcp`](https://www.npmjs.com/package/@coreui/docs-mcp). --- # React Data Grid Overview > A single kitchen-sink React Data Grid demo — 10,000 rows across fourteen columns with the toolbar, per-column filters, sizing, pinning, selection and a column chooser all turned on at once. This page is the kitchen-sink demo: one grid with every interaction feature turned on at once, so you can see how they compose. It renders **10,000 rows** across **fourteen columns** — four hidden by default — with the [toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/), [per-column filters](https://coreui.io/data-grid/react/docs/features/filtering/), [column sizing](https://coreui.io/data-grid/react/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/react/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/), the [column menu](https://coreui.io/data-grid/react/docs/columns/menu/), [row selection](https://coreui.io/data-grid/react/docs/features/row-selection/), multi-column [sorting](https://coreui.io/data-grid/react/docs/features/sorting/) and [pagination](https://coreui.io/data-grid/react/docs/features/pagination/). Every one of these is a single prop, documented on its own page — this demo just enables them together. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas', 'Roberts', 'Walker'] const departments = ['Engineering', 'Sales', 'Marketing', 'Support', 'Finance', 'People'] const roles = ['Manager', 'Lead', 'Senior', 'Junior', 'Contractor'] const statuses = ['active', 'invited', 'suspended'] const countries = ['Poland', 'Germany', 'France', 'Spain', 'Italy', 'United States', 'United Kingdom'] const cities = ['Warsaw', 'Berlin', 'Paris', 'Madrid', 'Rome', 'New York', 'London'] const badges: Record = { active: 'success', invited: 'info', suspended: 'danger' } const currency = (value: unknown) => Number(value).toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }) const date = (value: unknown) => new Date(value as string).toLocaleDateString('en-US') export const DataGridOverviewExample = () => { const items = useMemo( () => Array.from({ length: 10000 }, (_, i) => ({ id: i + 1, name: `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}`, email: `user${i + 1}@example.com`, department: departments[i % departments.length], role: roles[i % roles.length], status: statuses[i % statuses.length], salary: 45000 + ((i % 60) * 1500), rating: ((i % 9) + 1) / 2, projects: (i % 24) + 1, country: countries[i % countries.length], city: cities[i % cities.length], startDate: new Date(2021, i % 12, (i % 28) + 1).toISOString(), lastActive: new Date(2026, i % 6, (i % 27) + 1).toISOString(), phone: `+1 555 ${String(1000 + (i % 9000))}` })), [] ) return ( {item.status as string} }, { key: 'salary', label: 'Salary', width: 130, filterType: 'number', formatter: currency }, { key: 'rating', label: 'Rating', width: 110, filterType: 'number' }, { key: 'projects', label: 'Projects', width: 120, filterType: 'number' }, { key: 'country', label: 'Country', width: 160, filterType: 'select' }, { key: 'city', label: 'City', width: 150 }, { key: 'startDate', label: 'Started', width: 140, filterType: 'date', formatter: date }, { key: 'lastActive', label: 'Last active', width: 140, filterType: 'date', formatter: date }, { key: 'phone', label: 'Phone', width: 160 } ]} items={items} itemKey={item => String(item.id)} columnFilters columnMenu columnOrder columnPinning={{ left: ['id'] }} columnSizing columnVisibility={{ projects: false, city: false, lastActive: false, phone: false }} rowSelection sorting={{ multiple: true }} pagination={{ pageSize: 20, pageSizeOptions: [10, 20, 50, 100] }} toolbar={{ columns: true, export: { filename: 'employees.csv' }, search: true }} /> ) } ``` ## What's turned on | Prop | Feature | | --- | --- | | `toolbar` | [Column chooser, CSV export and global search](https://coreui.io/data-grid/react/docs/features/toolbar/) | | `columnFilters` + `filterType` | [Per-column typed filters](https://coreui.io/data-grid/react/docs/features/filtering/) (text, number, date, select) | | `columnSizing` | [Drag-to-resize columns](https://coreui.io/data-grid/react/docs/columns/sizing/) | | `columnPinning` | [`id` pinned to the left edge](https://coreui.io/data-grid/react/docs/columns/pinning/) | | `columnOrder` | [Drag-and-drop column reordering](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) | | `columnVisibility` | [Four columns hidden until you show them](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) | | `columnMenu` | [Per-header sort / pin / hide menu](https://coreui.io/data-grid/react/docs/columns/menu/) | | `rowSelection` | [Checkbox column with select-all](https://coreui.io/data-grid/react/docs/features/row-selection/) | | `sorting={{ multiple: true }}` | [Shift-click multi-column sort](https://coreui.io/data-grid/react/docs/features/sorting/) | | `pagination` | [Page size switcher](https://coreui.io/data-grid/react/docs/features/pagination/) | ## Presentation `salary` and the two dates use a column [`formatter`](https://coreui.io/data-grid/react/docs/columns/overview/) so the displayed value — and the [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/) — reads as currency and localized dates. The `status` column uses [`render`](https://coreui.io/data-grid/react/docs/columns/overview/) to draw a colored badge. Everything else is the raw value. To scale past what fits in the browser, keep this UI and hand data fetching off to your API with [server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/). --- # React Data Grid Virtualization > Row virtualization renders only the visible window of rows, so the React Data Grid stays fast with 100,000 rows and beyond — sorting, filtering and selection run across the full dataset. Virtualization keeps the DOM small no matter how large the dataset is: only the rows currently in view (plus a small buffer) are rendered as real elements. Reach for it whenever you bind more rows than the browser can comfortably paint at once — a few thousand and up. It is **on by default** (`virtualization`) and is mutually exclusive with [pagination](https://coreui.io/data-grid/react/docs/features/pagination/). ## 100,000 rows, virtualized Only the visible window of rows exists in the DOM — scroll, sort, filter and select across the full dataset. This live demo runs on 100,000 generated rows. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] const statuses = ['active', 'pending', 'banned'] export const DataGridVirtualExample = () => { const items = useMemo( () => Array.from({ length: 100_000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length], status: statuses[i % statuses.length], score: (i * 37) % 1000 } }), [] ) return ( String(item.id)} columnFilters globalFilter rowSelection /> ) } ``` ## How it works The grid measures the scroll viewport and renders only the rows that intersect it. Two props tune the behavior: - `rowHeight` — the estimated row height in px (default `44`) the virtualizer uses to size the scroll area and decide how many rows fit. - `overscan` — extra rows rendered above and below the visible window (default `10`) to smooth fast scrolling. Raise it if you see blank rows while flinging; lower it to shave DOM nodes. Fixed row height is assumed in this release — dynamic per-row heights are on the [roadmap](https://coreui.io/data-grid/react/docs/resources/roadmap/). For datasets larger than browser memory, hand paging to your backend with [server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/). See the [Performance guide](https://coreui.io/data-grid/react/docs/guides/performance/) for tuning advice. --- # React Data Grid Sorting > Sort the React Data Grid by clicking a header, add multi-column sorting with shift+click, and opt individual columns out. Sorting is **on by default**. Click a header to toggle ascending ↔ descending (set `resetable: true` for a third click that clears the sort); the sort runs across the whole dataset, not just the visible window. Shift+click a second header to sort by more than one column at once. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] export const DataGridSortingExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length], score: (i * 37) % 1000 } }), [] ) return ( String(item.id)} sorting={{ multiple: true }} pagination={{ pageSize: 10 }} /> ) } ``` ## Usage ```tsx ``` Pass an object to tune the behavior: | Key | Type | Default | Description | | --- | --- | --- | --- | | `multiple` | `boolean` | `true` | Allow sorting by more than one column with shift+click. | | `resetable` | `boolean` | `false` | Allow a third click to clear the column's sort. | Disable sorting for a single column with `sortable: false` in its [definition](https://coreui.io/data-grid/react/docs/api/columns/), or turn it off entirely with `sorting={false}`. ## Sort icon visibility Only the **active** sort direction (the ascending/descending arrow) shows by default — the neutral, unsorted indicator stays hidden to keep headers clean. Set `sorterVisibility` to surface it on every sortable column: | Value | Behavior | | --- | --- | | `'always'` | The neutral icon is always visible (dimmed) on sortable columns. | | `'hover'` | The neutral icon appears when the header is hovered or focused. | ```tsx ``` ## Multi-column sorting With `multiple` enabled (the default), **shift+click** a second header to add it to the sort instead of replacing the first. The sort priority follows click order. Below, click **Department**, then shift+click **Salary** — the readout shows the active sort and its priority. ```html import { CDataGrid, type SortingState } from '@coreui/react-data-grid' import { useMemo, useState } from 'react' const departments = ['Engineering', 'Design', 'Sales'] const labels: Record = { name: 'Name', department: 'Department', salary: 'Salary' } export const DataGridSortingMultiExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, department: departments[i % departments.length], salary: 40000 + ((i * 137) % 60000) })), [] ) const [sorting, setSorting] = useState([]) return ( <> String(item.id)} sorting={{ multiple: true }} pagination={{ pageSize: 10 }} onSortingChange={setSorting} />

{sorting.length ? `Sorted by: ${sorting .map((sort, index) => `${index + 1}. ${labels[sort.id]} ${sort.desc ? '↓' : '↑'}`) .join(' ')}` : 'Click a header, then shift+click another to sort by multiple columns.'}

) } ``` ## Reacting to sort changes Each change calls `onSortingChange` with the grid's `sorting` state: ```tsx { console.log(sorting) // [{ id: 'name', desc: false }] }} /> ``` In [server-side mode](https://coreui.io/data-grid/react/docs/features/server-side-data/) the same `sorting` state is handed to your `dataProvider` so your API does the ordering. --- # React Data Grid Filtering > Filter the React Data Grid with a per-column filter row, a global search input, custom filter components and custom matching predicates. The Data Grid filters on two levels. `columnFilters` renders a filter row in the header with one input per filterable column; `globalFilter` adds a single search input above the grid that matches across every column. Both narrow the rows client-side (or feed your [`dataProvider`](https://coreui.io/data-grid/react/docs/features/server-side-data/) in server-side mode). Opt a column out with `filterable: false`. ```tsx ``` ## Filter menu With `columnFilters` enabled every filterable column gets a **filter button** (funnel icon, configurable via `filterIcon`) in its header, opening a filter dialog. The dialog offers typed operators driven by `column.filterType` — `text` (default), `number` and `date` get operator conditions (up to two, joined **AND**/**OR**), while `select` renders a faceted checkbox list of the column's actual values. An active filter marks the button with a dot; the structured value travels inside the grid's `columnFilters` state, so a [`dataProvider`](https://coreui.io/data-grid/react/docs/features/server-side-data/) receives it verbatim. Try it below: filter *Salary* with `Between`, *Department* with the set filter, or combine two conditions on *Hired*. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const departments = ['Engineering', 'Design', 'Sales', 'Support'] export const DataGridFilterMenuExample = () => { const items = useMemo( () => Array.from({ length: 200 }, (_, i) => ({ name: `Employee ${String(i + 1).padStart(3, '0')}`, department: departments[i % departments.length], salary: 40_000 + ((i * 977) % 60_000), hired: new Date(2020, i % 60, (i % 27) + 1).toISOString().slice(0, 10) })), [] ) return ( ) } ``` ## Custom column filters Two per-column hooks extend the built-in filter row. `filter` replaces the text input with your own component receiving `{ column, table, labels }` — the `column` hands you `setFilterValue()`, `getFilterValue()` and `column.getFacetedUniqueValues()` for building selects from the actual data. `filterFn` swaps the matching logic (default: case-insensitive contains, or the [filter dialog](https://coreui.io/data-grid/react/docs/features/filtering/#filter-menu)'s structured value) for your own predicate `(value, filterValue, item) => boolean` — with a custom UI it receives exactly what that UI committed. Here Role pairs a faceted select with an exact-match predicate, while Score just declares `filterType: 'number'` and gets the built-in numeric dialog. ```html import { CDataGrid } from '@coreui/react-data-grid' import type { CDataGridColumnFilterProps } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] // Custom filter UI — a component receiving { column, table, labels } const RoleFilter = ({ column }: CDataGridColumnFilterProps) => ( ) export const DataGridCustomFiltersExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length], score: (i * 37) % 1000 } }), [] ) return ( value === filterValue }, { key: 'score', label: 'Score', width: 140, filterType: 'number' } ]} items={items} itemKey={item => String(item.id)} pagination={{ pageSize: 10 }} /> ) } ``` ## Custom filter API | Column key | Type | Description | | --- | --- | --- | | `filterable` | `boolean` | Set `false` to remove the column's filter button. | | `filter` | `ComponentType<{ column, table, labels }>` | Custom filter component replacing the default text input (requires `columnFilters`). | | `filterFn` | `(value, filterValue, item) => boolean` | Custom predicate replacing the built-in matching; with the filter dialog it receives the structured value. | Every filter change calls `onFilterChange` with `(columnFilters, globalFilter)`. A custom [toolbar slot](https://coreui.io/data-grid/react/docs/features/slots/) that hosts its own search box still needs `globalFilter` for the query to reach the grid. --- # React Data Grid Row Selection > Add a checkbox column to the React Data Grid with select-all and shift+click range selection, keyed to a stable row id so selection survives sorting, filtering and paging. `rowSelection` adds a checkbox column with a select-all header and shift+click range selection. Selection is keyed by [`itemKey`](https://coreui.io/data-grid/react/docs/api/options/), so a selected row stays selected as the user sorts, filters or pages — set `itemKey` whenever you enable selection. Select a few rows below, then page or sort — the selection holds. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo, useState } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] export const DataGridRowSelectionExample = () => { const [selectedCount, setSelectedCount] = useState(0) const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( <> String(item.id)} rowSelection pagination={{ pageSize: 10 }} onSelectionChange={rowSelection => setSelectedCount(Object.keys(rowSelection).length)} />

{selectedCount ? `${selectedCount} rows selected` : 'No rows selected'}

) } ``` ## Usage ```tsx String(item.id)} // required for stable selection rowSelection /> ``` Pass an object to configure it: | Key | Type | Default | Description | | --- | --- | --- | --- | | `selectAll` | `boolean` | `true` | Show the select-all checkbox in the header. | The [pagination demo](https://coreui.io/data-grid/react/docs/features/pagination/) shows selection in action alongside a custom actions column. ## Reading the selection Listen for changes with `onSelectionChange` — it receives the row-selection state and the selected item objects: ```tsx String(item.id)} rowSelection onSelectionChange={(rowSelection, selectedItems) => { console.log(selectedItems) console.log(rowSelection) // row-selection state }} /> ``` ## With pinning and server-side data When a column is pinned left and selection is on, the checkbox column travels with it — see [Column pinning](https://coreui.io/data-grid/react/docs/columns/pinning/). In [server-side mode](https://coreui.io/data-grid/react/docs/features/server-side-data/), selection is id-keyed so it survives page changes, but `selectedItems` contains only the items present in the current page's data. --- # React Data Grid Keyboard Navigation > APG grid keyboard navigation for the React Data Grid — role="grid", a roving-tabindex active cell and arrow-key movement across header and data cells. `cellNavigation` switches the grid to the ARIA [grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/): the table gains `role="grid"`, exactly one cell is tabbable at a time (a roving tabindex), and the arrow keys move a visible active cell across header and data cells. Click a cell below, then navigate with the keyboard. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const roles = ['admin', 'editor', 'viewer'] export const DataGridKeyboardNavigationExample = () => { const items = useMemo( () => Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })), [] ) return ( String(item.id)} rowSelection /> ) } ``` ## Usage ```tsx ``` `cellNavigation` is off by default — without it the grid keeps native table semantics and the [accessible chrome](https://coreui.io/data-grid/react/docs/guides/accessibility/) it always had. [Inline editing](https://coreui.io/data-grid/react/docs/features/editing/) requires the active-cell model, so `editing` enables `cellNavigation` automatically. ## Keys | Key | Action | | --- | --- | | Arrow keys | Move one cell; stop at the edges (no wrap). The header label row is row one — Arrow Up from the first data row reaches it. | | Home / End | First / last cell in the row. | | Ctrl+Home / Ctrl+End | First / last data cell of the grid. | | PageUp / PageDown | Move one viewport when virtualized; move one page (and flip it) under pagination. | | Tab / Shift+Tab | Leave the grid — the whole grid is a single tab stop. | | Enter | Toggle sort on a sortable header cell; descend into a data cell's interactive content (links, buttons); start [editing](https://coreui.io/data-grid/react/docs/features/editing/) an editable cell. | | Escape | Ascend from cell content back to the cell. | | Space | Toggle [row selection](https://coreui.io/data-grid/react/docs/features/row-selection/) on the active row. | ## Focus and virtualization The active cell is state, not DOM: with [virtualization](https://coreui.io/data-grid/react/docs/features/virtualization/) on, navigating to an off-window row scrolls it into view first and focuses it once it renders. If the focused row is recycled out of the window while you scroll, focus parks on the viewport and the next navigation key brings the active cell back into view. ## Styling The active cell shows an inset focus ring driven by two component tokens: ```css .data-grid { --cui-data-grid-focus-ring-width: 2px; --cui-data-grid-focus-ring-color: var(--cui-primary); } ``` --- # React Data Grid Inline Editing > Inline cell editing for the React Data Grid — built-in text, number and select editors, validation, and a popup contract for rich editors like date pickers and multi-selects. `editing` turns cells editable in place. Press Enter or F2 on the active cell — or double-click any cell — to start; Enter commits, Escape cancels, Tab commits and moves to the next editable cell. Editing builds on [keyboard navigation](https://coreui.io/data-grid/react/docs/features/keyboard-navigation/), so `editing` enables `cellNavigation` automatically. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useState } from 'react' const roles = ['admin', 'editor', 'viewer'] const initialItems = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + (i % 40), role: roles[i % roles.length] })) export const DataGridEditingExample = () => { const [items, setItems] = useState(initialItems) return ( (value === '' ? 'Name is required' : true) }, { key: 'age', label: 'Age', width: 110, editable: { type: 'number', min: 0, max: 120 } }, { key: 'role', label: 'Role', width: 130, editable: { type: 'select', options: roles } } ]} editing items={items} itemKey={item => String(item.id)} onEditCommit={({ item, columnId, value }) => // The grid never mutates items - apply the committed change yourself. setItems(current => current.map(row => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } /> ) } ``` ## Usage Editing is opt-in per column — `editable` picks a built-in editor, a custom `editor` component is itself the opt-in: ```tsx ``` ## The app owns the data The grid never mutates `items`. A commit calls `onEditCommit` with `{ item, columnId, value, previousValue }` — apply the change to your state and the grid re-renders (server-side, PATCH and refetch): ```tsx setItems((current) => current.map((row) => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } /> ``` `onEditStart` and `onEditCancel` fire around it with `(item, columnId)`. ## Validation `editValidate` gates the commit. Return `true` to accept, or a message to block it — the editor gets `aria-invalid`, the `is-invalid` class and an `aria-errormessage` pointing at the message: ```tsx { key: 'name', editable: true, editValidate: (value, item) => value !== '' || 'Name is required' } ``` An invalid value keeps the editor open; Escape still cancels. ## Custom editors `editor` replaces the built-in input with your own component. It renders with the editing context and registers the imperative bits of the contract through `handleRef`: ```tsx import type { CDataGridEditorProps } from '@coreui/react-data-grid' import { useEffect, useRef } from 'react' const NoteEditor = ({ value, handleRef }: CDataGridEditorProps) => { const inputRef = useRef(null) useEffect(() => { const input = inputRef.current handleRef({ focus: () => input?.select(), getValue: () => input?.value, }) return () => handleRef(null) }, []) return } // columns: [{ key: 'note', editor: NoteEditor }] ``` The component receives `{ item, column, value, invalid, labels, commit, cancel, handleRef }`; the handle can provide `getValue` (feeds Enter/Tab/blur/outside commits), `focus`, and `contains` (extends the edit scope to overlays portaled outside the cell). ## Rich editors — date pickers, autocompletes, multi-selects Editors whose UI extends beyond the cell — a date range picker's calendar, a multi-select's listbox — set `editorPopup: true` on the column. The grid renders them in a `.data-grid-editor-popup` layer anchored to the cell (min-width = cell width), so a tall editor never reflows the row, and the cell keeps its content underneath. Three rules make components like `@coreui/react-pro`'s `CDateRangePicker`, `CTimePicker`, `CAutocomplete` and `CMultiSelect` work as editors: - **Commit on outside, not on blur alone.** Picking a date in a calendar blurs the input mid-edit — so the grid commits on pointerdown *outside the edit scope* (cell + popup + anything `contains()` claims). Keep the component's overlay inside the popup (its `container` prop), or claim a portaled overlay via the handle's `contains()`. - **One Escape, one layer.** An Escape the editor consumed (calling `preventDefault()` to close its own overlay) never cancels the edit — only the next, unconsumed Escape does. - **Values are not scalars.** `getValue()` can return anything — a range picker commits `{ startDate, endDate }`, a multi-select commits an array; `onEditCommit` passes it through untouched. ```tsx const PeriodEditor = ({ value, handleRef }: CDataGridEditorProps) => { const range = useRef(value as { startDate?: string; endDate?: string } | undefined) useEffect(() => { handleRef({ getValue: () => range.current }) return () => handleRef(null) }, []) return ( (range.current = { ...range.current, startDate })} onEndDateChange={(endDate) => (range.current = { ...range.current, endDate })} /> ) } // columns: [{ key: 'period', editor: PeriodEditor, editorPopup: true }] ``` ## Interaction details - Scrolling the editing row out of the [virtualized](https://coreui.io/data-grid/react/docs/features/virtualization/) window commits the draft; so do sort, filter and page transitions. - Replacing `items` and [server-side](https://coreui.io/data-grid/react/docs/features/server-side-data/) data loads cancel an in-flight edit — the incoming data owns the cell. - Shift+Tab commits and moves to the previous editable cell. --- # React Data Grid Undo & Redo > Undo and redo inline-edit commits in the React Data Grid — toolbar buttons and Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y, with the app staying the single source of truth. `history` keeps an undo/redo stack of [inline editing](https://coreui.io/data-grid/react/docs/features/editing/) commits. Undo with the toolbar button or Ctrl+Z (⌘Z on macOS), redo with Ctrl+Shift+Z or Ctrl+Y. Edit a few cells below, then undo your way back. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useState } from 'react' const roles = ['admin', 'editor', 'viewer'] const initialItems = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + (i % 40), role: roles[i % roles.length] })) export const DataGridHistoryExample = () => { const [items, setItems] = useState(initialItems) return ( String(item.id)} toolbar={{ history: true }} onEditCommit={({ item, columnId, value }) => // Undo/redo re-call onEditCommit with the values swapped, so the // handler that applies an edit also reverts it. setItems(current => current.map(row => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } /> ) } ``` ## Usage ```tsx ``` The buttons stay disabled while their stack is empty; each undo/redo announces through the ARIA live region (`undoneAnnouncement`/`redoneAnnouncement` labels). ## How undo works — the app stays in charge The grid never mutates `items`. An undo **re-calls `onEditCommit`** with `value` and `previousValue` swapped (redo re-calls the original), so the same handler that applied the edit reverts it — no second code path: ```tsx onEditCommit={({ item, columnId, value }) => setItems((current) => current.map((row) => (row.id === item.id ? { ...row, [columnId]: value } : row)) ) } ``` History entries reference rows by id, so they survive the immutable updates this pattern produces. If a row disappears from the data entirely, its entries are dropped. New commits clear the redo stack; the stack holds the last 100 edits. Following MUI and AG Grid, undo/redo covers **data edits** — view changes (sorting, filters, column layout) are not tracked; persist those with [`stateKey`](https://coreui.io/data-grid/react/docs/features/state/) instead. --- # React Data Grid Save & Restore State > Persist the React Data Grid view — sorting, filters, column order, sizing, visibility, pinning, selection and page — to localStorage with a single prop. `stateKey` makes the grid's view persistent: every user-adjustable state slice — sorting, column filters and the global search, column order, sizing, visibility and pinning, row selection and the page — **autosaves to `localStorage`** under the key (debounced) and **restores automatically on the next mount**. No buttons, nothing to wire up. Sort or resize below, reload the page — the grid comes back as you left it. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const roles = ['admin', 'editor', 'viewer'] export const DataGridStateExample = () => { const items = useMemo( () => Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })), [] ) return ( String(item.id)} stateKey="docs-state-demo-react" toolbar /> ) } ``` ## Usage ```tsx String(item.id)} // keeps restored selection meaningful stateKey="users-grid" // localStorage identity — that's it /> ``` Every state change writes the snapshot (debounced at 250 ms) to `localStorage` under `coreui-data-grid:`; the next grid mounted with the same key restores it before first render. ## Reading the snapshot The saved snapshot is `{ sorting, columnFilters, globalFilter, columnOrder, columnPinning, columnSizing, columnVisibility, pagination, rowSelection }` (the `DataGridState` type). For your own storage, collect the slices from the `onXxxChange` callbacks — or read them off the exposed [`tableRef`](https://coreui.io/data-grid/react/docs/api/options/) — and hand a saved snapshot back by mounting the grid with `stateKey` pointing at it. ## What is (and isn't) state - Row selection restores by row id — set [`itemKey`](https://coreui.io/data-grid/react/docs/api/options/) or the restored ids point at positions, not rows. - In [server-side mode](https://coreui.io/data-grid/react/docs/features/server-side-data/) the restored sorting/filters/page are applied **before the first request**, so the grid loads straight into the saved view. - Scroll position and an in-flight [edit](https://coreui.io/data-grid/react/docs/features/editing/) draft are not state. Data edits aren't either — undoing those is what [undo & redo](https://coreui.io/data-grid/react/docs/features/history/) is for. - Restoring calls the matching `onXxxChange` callbacks for every slice that changed — your app sees a restore exactly like user interaction. --- # React Data Grid Pagination > Page through the React Data Grid with a page-size selector, range info and CoreUI pagination controls instead of virtualized scrolling. Pagination breaks the dataset into fixed-size pages with familiar Previous/Next controls. Use it when users expect discrete pages, when you want a predictable page height on the screen, or as the client-side counterpart to [server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/) (which always paginates). Pagination is **mutually exclusive with [virtualization](https://coreui.io/data-grid/react/docs/features/virtualization/)** — turning it on switches the grid out of windowed scrolling. ## Pagination mode Instead of virtualization the grid can paginate — with a page-size selector, range info and CoreUI `.pagination` controls. This demo also shows a custom `formatter`, an actions column built with `render`, and row selection. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] export const DataGridPaginationExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( String(value).toUpperCase() }, { key: 'actions', label: '', sortable: false, filterable: false, width: 120, render: item => ( ) } ]} items={items} itemKey={item => String(item.id)} pagination={{ pageSize: 10 }} rowSelection /> ) } ``` ## Options Pass `pagination` for the defaults, or an object to configure it: | Key | Type | Default | Description | | --- | --- | --- | --- | | `pageSize` | `number` | `10` | Rows per page. | | `pageSizeOptions` | `number[]` | `[5, 10, 20, 50]` | Choices in the page-size selector. | | `position` | `'top' \| 'bottom' \| 'both'` | `'bottom'` | Where the pagination bar renders. | | `info` | `boolean` | `true` | Shows the `Showing X–Y of Z` range summary. | Every page change calls `onPaginationChange` with the grid's `pagination` state. Drive paging yourself through the [headless table](https://coreui.io/data-grid/react/docs/api/headless/) — e.g. `tableRef.current?.setPageIndex(3)`. --- # React Data Grid Server-Side Data > Delegate sorting, filtering and pagination to your API with a single dataProvider prop — the React Data Grid fetches one debounced request per state change and drops stale responses. Client-side mode ends where the browser's memory does. When your dataset lives in a database with tens of thousands of rows or more, hand the work to your backend: set `dataProvider` and the grid stops computing row models locally and asks your API for each page instead. This is the single biggest adoption unlocker for large data. ## Server-side data Set `dataProvider` and the grid switches to server-side mode — every sorting, filtering or pagination change triggers one debounced request (stale responses are dropped automatically), a loading overlay covers the viewport and totals come from your `totalRows`. Server-side mode implies pagination. This live demo hits CoreUI's public demo API (`apitest.coreui.io/demos/users`) — 10,000+ records sorted, filtered and paged server-side. ```html import { CDataGrid } from '@coreui/react-data-grid' import type { DataGridDataRequest } from '@coreui/react-data-grid' import { useCallback } from 'react' const fetchUsers = async ({ sorting, columnFilters, pagination }: DataGridDataRequest) => { const params = new URLSearchParams({ offset: String(pagination.pageIndex * pagination.pageSize), limit: String(pagination.pageSize) }) for (const { id, value } of columnFilters) { params.append(id, String(value)) } const [sort] = sorting if (sort) { params.append('sort', `${sort.id}%${sort.desc ? 'desc' : 'asc'}`) } const response = await fetch(`https://apitest.coreui.io/demos/users?${params}`) const result = await response.json() const totalRows = Number(result.number_of_matching_records) return { items: totalRows ? result.records : [], totalRows } } export const DataGridServerExample = () => { const dataProvider = useCallback(fetchUsers, []) return ( String(item.id)} columnFilters dataProvider={dataProvider} /> ) } ``` ## The dataProvider contract ```tsx { // fetch from your API and return the matching page return { items, totalRows } }} pagination // server-side mode implies pagination /> ``` - **One request per state change.** `sorting`, `columnFilters`, `globalFilter` and `pagination` arrive as structured state; return the page of `items` plus the full `totalRows` count so the pager can render. - **Debounced & race-safe.** Rapid changes coalesce into one request and only the latest response is applied — stale ones are dropped by request id. - **Loading UX.** A `.data-grid-loading` overlay with a spinner covers the viewport and `aria-busy` is set while a request is in flight (`labels.loading`). - **Errors.** A rejected fetch calls `onDataError` with the `error`, shows the empty state (`labels.loadError`) and leaves the grid interactive. - **Success.** Each load calls `onDataLoad` with `{ items, totalRows }`. ## Selection semantics `rowSelection` is keyed by [`itemKey`](https://coreui.io/data-grid/react/docs/api/options/), so a selection survives page changes by design. `onSelectionChange`'s `selectedItems` contains only the items present in the current page's data — the grid does not cache full objects for pages it has scrolled past. The same page-bound rule applies to [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/): in server-side mode every scope (`filtered`, `all`, `selected`) exports the currently loaded page only. When the [filter menu](https://coreui.io/data-grid/react/docs/features/filtering/) is used, a column's entry in `columnFilters` carries the structured value — `{ conditions: [{ operator, value, value2? }], join: 'and' | 'or' }` for typed operators or `{ operator: 'in', value: [...] }` for the set filter — instead of a plain string. Servers should handle both shapes. --- # React Data Grid Toolbar > Add a built-in React Data Grid toolbar with a column chooser, CSV export button and global search — enabled with a single prop or configured granularly. The `toolbar` prop adds a built-in chrome row above the grid with a **column chooser**, a **CSV export** button, **Undo/Redo** buttons (with [`history`](https://coreui.io/data-grid/react/docs/features/history/)) and a **global search** input. Each action drives an existing feature, so the toolbar is the ready-made UI you would otherwise build with the [`toolbar` slot](https://coreui.io/data-grid/react/docs/features/slots/). ```tsx ``` `toolbar` (boolean `true`) enables each action whose underlying feature is on: **columns** needs [`columnVisibility`](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/), **export** is always available, **undo/redo** needs [`history`](https://coreui.io/data-grid/react/docs/features/history/), and **search** turns on the global filter. Pass a granular object to pick actions individually — `search: true` is the same input as [`globalFilter`](https://coreui.io/data-grid/react/docs/features/filtering/), so keep using whichever reads better. ```tsx ``` ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const roles = ['admin', 'editor', 'viewer'] export const DataGridToolbarExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })), [] ) return ( String(item.id)} columnVisibility pagination={{ pageSize: 10 }} toolbar={{ columns: true, export: { filename: 'users.csv' }, search: true }} /> ) } ``` ## Column chooser With `toolbar.columns` enabled the columns button opens a popup listing every leaf column in visual order with a checkbox. Toggling a checkbox calls `column.toggleVisibility()` live — there is no Apply step. Columns marked `hideable: false` stay checked and disabled. The footer offers **Show all** and **Reset** (Reset restores the initial `columnVisibility` object). Visibility changes call `onVisibilityChange` just like the [column menu](https://coreui.io/data-grid/react/docs/columns/menu/). ## Export The export button downloads the current view as CSV by calling [`downloadCsv(table, { scope: 'filtered' })`](https://coreui.io/data-grid/react/docs/features/csv-export/). Pass a [`CsvDownloadOptions`](https://coreui.io/data-grid/react/docs/features/csv-export/) object as `toolbar.export` to set `filename`, `delimiter`, `bom`, `sanitize` or `scope`. ## Icons The toolbar buttons use the CoreUI icon set, overridable per-instance with the `toolbarColumnsIcon`, `toolbarExportIcon`, `toolbarUndoIcon` and `toolbarRedoIcon` props (any `ReactNode`, like every other icon prop). ## Custom toolbar The `toolbar` slot still **replaces** the whole toolbar; the built-in buttons are not composable into a custom slot. For a fully custom toolbar — including a hand-built column chooser — use the [`toolbar` slot](https://coreui.io/data-grid/react/docs/features/slots/) with the headless `table` and public helpers (`downloadCsv`, `column.toggleVisibility`). See the [column ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) page for a slot-based chooser. --- # React Data Grid Slots & Custom Rendering > Replace the React Data Grid's toolbar, pagination and empty-state chrome with your own components through the slots prop. Slots let you swap the grid's built-in chrome — `toolbar`, `pagination` and `empty` — for your own UI while the grid keeps driving state through the headless table. Reach for a slot when the default control isn't enough: a custom toolbar with extra actions, a bespoke pager, or a richer empty state. For custom *cell* content, use a column's [`formatter` or `render`](https://coreui.io/data-grid/react/docs/columns/overview/) instead. ## Custom slots Replace the grid's chrome — `toolbar`, `pagination` and `empty` — with your own components. Each slot is a component that receives `{ table, labels }` and renders in place of the built-in module; it re-renders with the grid, so it always reflects current state. This demo swaps the built-in pagination for a minimal Previous/Next control driven through the headless `table`. ```html import { CDataGrid } from '@coreui/react-data-grid' import type { CDataGridSlotProps } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] const Pagination = ({ table }: CDataGridSlotProps) => { const { pageIndex } = table.getState().pagination return (
Page {pageIndex + 1} of {table.getPageCount()} · {table.getRowCount()} items
) } export const DataGridSlotsExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( String(item.id)} pagination={{ pageSize: 10 }} slots={{ pagination: Pagination }} /> ) } ``` With `pagination={{ position: 'both' }}` the slot component renders once per position. A custom toolbar that hosts its own global search still needs `globalFilter` for the query to reach the grid. The `empty` slot also renders when a server load fails; use the `onDataError` callback to tell the two apart. ## Slot contract | Slot | Replaces | Signature | | --- | --- | --- | | `toolbar` | The toolbar above the grid | `ComponentType<{ table, labels }>` | | `pagination` | The pagination bar | same | | `empty` | The no-rows / load-error state | same | - `table` — the headless [`table`](https://coreui.io/data-grid/react/docs/api/headless/) instance; read current state and call its methods to drive the grid. - `labels` — the resolved [localization](https://coreui.io/data-grid/react/docs/customization/localization/) strings. - The component re-renders on every grid render, so it always reflects current state — no manual update or teardown hooks needed. --- # React Data Grid CSV Export > Export React Data Grid rows to an RFC-4180 CSV string or file, with scope, delimiter, BOM and formula-injection sanitization options. Give users a one-click download of what they're looking at. `exportCsv(table, options)` returns a spec-compliant CSV string and `downloadCsv(table, options)` saves it as a file, both respecting the grid's current layout and filters. The pure CSV helper also ships standalone for server-side or headless use with no runtime dependencies. ## CSV export `exportCsv(table, options)` returns an RFC-4180 string; `downloadCsv(table, options)` saves it as a file — both take the headless `table` (via `tableRef`). Exported columns follow the rendered layout (pinning, order, visibility). Values use each column's `formatter` (never `render`); `scope` picks `'filtered'` (default, all matching rows), `'all'` (ignores filters) or `'selected'`; `delimiter` and `bom` (Excel-friendly UTF-8) are configurable. Set `sanitize: true` to guard against CSV formula injection (prefixes fields starting with `=`, `+`, `-` or `@` with an apostrophe) when exporting untrusted data. Server-side grids export the rows currently in memory. Both helpers are published at `@coreui/data-grid/csv` — no runtime dependencies — and re-exported from `@coreui/react-data-grid`. ```html import { CDataGrid, downloadCsv } from '@coreui/react-data-grid' import { useMemo, useRef } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] export const DataGridCsvExample = () => { const tableRef = useRef[0]>(null) const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( <>
String(value).toUpperCase() } ]} items={items} itemKey={item => String(item.id)} columnFilters pagination={{ pageSize: 10 }} rowSelection tableRef={tableRef} /> ) } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `scope` | `'filtered' \| 'all' \| 'selected'` | `'filtered'` | Which rows to export. | | `delimiter` | `string` | `','` | Field delimiter. | | `bom` | `boolean` | `false` | Prepend a UTF-8 BOM for Excel. | | `sanitize` | `boolean` | `false` | Prefix formula-triggering fields (`= + - @`) with `'` to prevent CSV injection. | | `filename` | `string` | `'export.csv'` | `downloadCsv` only — the download filename. | --- # React Data Grid Columns Overview > Define React Data Grid columns — keys, labels, cheap value formatting with formatter, and rich cell content with render. Columns are defined by the [`columns`](https://coreui.io/data-grid/react/docs/api/options/) prop. Each entry maps a `key` in your data to a header and a cell. This page covers the essentials; per-column features live on their own pages: [sizing](https://coreui.io/data-grid/react/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/react/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) and the [column menu](https://coreui.io/data-grid/react/docs/columns/menu/). ## Defining columns ```tsx ``` `key` is the property read from each item and doubles as the column id. `label` is the header text — it falls back to `key` when omitted. ## Formatting values Use `formatter` to transform the displayed value. It's cheap and stays on the scroll hot path, so it's the right tool for dates, numbers and currency: ```tsx { key: 'createdAt', label: 'Created', formatter: (value) => new Date(value).toLocaleDateString(), } ``` `formatter` output is also what [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/) writes. ## Rich cell content Use `render` for full custom cell content — action buttons, badges, links. `render` returns JSX and is **never** used for CSV export: ```tsx { key: 'actions', label: '', render: (item) => ( ), } ``` Use `formatter` **or** `render` per column — `formatter` for values on the hot path, `render` for interactive content. See the [column API](https://coreui.io/data-grid/react/docs/api/columns/) for every key. --- # React Data Grid Column Sizing > Add drag-to-resize handles to React Data Grid header cells, with live or on-release width updates and per-column opt-out. Let users widen a column to read long values or shrink one they don't care about. `columnSizing` adds a drag handle to every resizable header cell; widths persist in the grid's state and can be committed live or on release. ## Column resizing Set `columnSizing` to add a drag handle to the right edge of every header cell. Widths follow the pointer live (`columnSizing={{ mode: 'onEnd' }}` commits them on release instead), the grid scrolls horizontally once the columns outgrow the viewport, and `column.width` seeds the starting width. Opt a column out with `resizable: false` — here the `#` column stays fixed. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] export const DataGridResizingExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( String(item.id)} columnSizing pagination={{ pageSize: 10 }} /> ) } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnSizing` | `boolean \| { mode?: 'onChange' \| 'onEnd' }` | `false` | Enables resize handles. `mode` controls whether widths update while dragging (`'onChange'`, the default) or on release (`'onEnd'`). | | `resizable` (column) | `boolean` | `true` | Set `false` to drop the resize handle for a column. | | `width` (column) | `number` | — | Seeds the starting width in pixels, e.g. `200`. | Resizing calls `onSizingChange` with the grid's `columnSizing` state. --- # React Data Grid Column Pinning > Freeze React Data Grid columns to the left or right edge so they stay visible while the rest of the grid scrolls horizontally. Keep an identifier or an actions column in view while users scroll a wide grid sideways. Pinning freezes columns against the left or right edge with sticky positioning; everything else scrolls between them. ## Column pinning Freeze columns against the left or right edge with `columnPinning={{ left, right }}` — they stay put while the rest of the grid scrolls horizontally. Pass `columnPinning` on its own to enable the feature and pin later through the headless table (`tableRef.current?.setColumnPinning(...)`). When a column is pinned left and selection is on, the checkbox column travels with it. The last left and first right column cast a shadow over the scrolling content. Pinning physically moves the column to its edge — the rendered order is always left-pinned, center, right-pinned. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const countries = ['Poland', 'Germany', 'France', 'Spain', 'Italy'] export const DataGridPinningExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, firstName: firstNames[i % firstNames.length], lastName: lastNames[i % lastNames.length], email: `${firstNames[i % firstNames.length].toLowerCase()}${i}@example.com`, country: countries[i % countries.length] })), [] ) return ( ( ) } ]} items={items} itemKey={item => String(item.id)} columnPinning={{ left: ['id'], right: ['actions'] }} rowSelection pagination={{ pageSize: 10 }} /> ) } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnPinning` | `boolean \| { left?: string[], right?: string[] }` | `false` | Freezes columns (by `key`) to the left/right edge. `true` enables the feature with no initial pins. | Pinning calls `onPinningChange` with the grid's `columnPinning` state. Reordering via [column ordering](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/) never crosses a pinning boundary. --- # React Data Grid Column Ordering & Visibility > Let users reorder React Data Grid columns by drag-and-drop and hide or show columns through a column chooser. Give users control over the layout: drag headers to reorder columns and toggle columns on and off to focus on what matters. Both features are driven through the headless table, so you can wire them into your own toolbar — as the column chooser in this demo does. ## Column ordering & visibility `columnOrder` makes headers draggable — drop one onto another to reorder (dragging never crosses a pinning boundary); pass an array for an initial order. `columnVisibility` enables hiding and showing columns through the headless table (`column.toggleVisibility()`, `column.getIsVisible()`); pass an object like `{ email: false }` to start with a column hidden. Opt a column out with `movable: false` / `hideable: false`. This demo adds a column chooser built entirely with the `toolbar` slot. ```html import { CDataGrid } from '@coreui/react-data-grid' import type { CDataGridSlotProps } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] // Column chooser built with the toolbar slot and the headless table const ColumnChooser = ({ table }: CDataGridSlotProps) => (
{table.getAllLeafColumns().map(column => column.getCanHide() ? ( ) : null )}
) export const DataGridOrderVisibilityExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( String(item.id)} columnOrder columnVisibility pagination={{ pageSize: 10 }} slots={{ toolbar: ColumnChooser }} /> ) } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnOrder` | `boolean \| string[]` | `false` | Drag-and-drop reordering; an array sets the initial order. Never crosses a pinning boundary. | | `columnVisibility` | `boolean \| Record` | `false` | Enables hiding/showing columns; an object sets the initial visibility. | | `movable` (column) | `boolean` | `true` | Set `false` to exclude a column from reordering. | | `hideable` (column) | `boolean` | `true` | Set `false` to prevent hiding a column. | Reordering calls `onOrderChange` with the `columnOrder` state; toggling visibility calls `onVisibilityChange` with the `columnVisibility` state. The [column menu](https://coreui.io/data-grid/react/docs/columns/menu/) offers a keyboard-accessible Move left/right as an alternative to drag-and-drop. --- # React Data Grid Column Menu > Add a per-column header menu to the React Data Grid that gathers sort, pin, move and hide actions behind one accessible ⋮ button, and customize its items with a builder. The column menu collects a column's actions — sort, pin, move, hide — behind a single ⋮ button in the header, so users don't have to discover drag-and-drop. It is the keyboard-accessible path to sorting, reordering and pinning, and it only shows the actions you've actually enabled. ## Column header menu `columnMenu` adds a ⋮ button to each header cell with the column's actions in one place — Sort ascending/descending/Unsort, Pin left/right/Unpin, Move left/right and Hide column. Items appear only for enabled features (`sorting`, `columnPinning`, `columnOrder`, `columnVisibility`) and respect per-column `sortable`/`movable`/`hideable` opt-outs; a column with no available action gets no button. Related actions are separated into groups by a divider. The menu follows the ARIA menu pattern (arrow keys, Home/End, Escape restores focus) — Move left/right is the keyboard-accessible way to reorder columns, complementing drag & drop. All labels are translatable via `labels`. ```html import { CDataGrid } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] export const DataGridColumnMenuExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( String(item.id)} columnMenu columnOrder columnPinning columnVisibility pagination={{ pageSize: 10 }} /> ) } ``` ## Customize the menu Pass a **builder function** to `columnMenu` to take full control of the items. It receives the column and the built-in actions for that column, and returns the final list — so you can reorder, drop or add items, per column: ```tsx columnMenu={({ column, defaultActions }) => CDataGridMenuAction[]} ``` - **Reorder** — return `defaultActions` in a different order. - **Hide an item** — filter it out by `key` (`'sort-asc'`, `'sort-desc'`, `'clear-sort'`, `'pin-left'`, `'pin-right'`, `'unpin'`, `'move-left'`, `'move-right'`, `'hide'`). - **Add your own** — push a new action object. - **Per column** — branch on `column` (the [column definition](https://coreui.io/data-grid/react/docs/api/options/)). A column whose builder returns at least one action shows the ⋮ button, even with no built-in feature enabled. Each action has this shape: ```tsx interface CDataGridMenuAction { key: string // unique id, also used to filter built-ins label: string // menu item text icon?: ReactNode // any React node — no sanitization needed disabled?: boolean // render but don't run group?: string // items with different groups get a divider between them run: () => void // invoked on click; the menu closes first } ``` Icons are plain React nodes (`ReactNode`), so you can pass any JSX — an inline ``, a ``, or any component. There is no `sanitize` option: React renders the nodes directly. ```html import { CDataGrid } from '@coreui/react-data-grid' import type { CDataGridColumnMenuContext, CDataGridMenuAction } from '@coreui/react-data-grid' import { useMemo } from 'react' const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson', 'Davies', 'Evans', 'Thomas'] const roles = ['admin', 'editor', 'viewer'] const copyIcon = ( ) export const DataGridColumnMenuCustomExample = () => { const items = useMemo( () => Array.from({ length: 1000 }, (_, i) => { const name = `${firstNames[i % firstNames.length]} ${lastNames[i % lastNames.length]}` return { id: i + 1, name, email: `${name.toLowerCase().replace(' ', '.')}${i}@example.com`, role: roles[i % roles.length] } }), [] ) return ( String(item.id)} // A builder receives the built-in actions plus the column, and returns the // final list — filter items, reorder them, or add your own. columnMenu={({ column, defaultActions }: CDataGridColumnMenuContext): CDataGridMenuAction[] => [ ...defaultActions, { key: 'copy-header', label: 'Copy header', group: 'custom', icon: copyIcon, run: () => navigator.clipboard?.writeText(column.label ?? column.key) } ]} columnOrder columnPinning columnVisibility pagination={{ pageSize: 10 }} /> ) } ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnMenu` | `boolean \| ((ctx) => CDataGridMenuAction[])` | `false` | Adds a per-column header menu. `true` builds it from the enabled features (sort/pin/move/hide); a builder `({ column, defaultActions }) => actions` returns the final item list (see [Customize the menu](#customize-the-menu)). | Every menu and header icon is overridable with its own `ReactNode` prop: `columnMenuIcon`, `sortAscendingIcon`, `sortDescendingIcon`, `sortNeutralIcon`, `pinLeftIcon`, `pinRightIcon`, `unpinIcon`, `moveLeftIcon`, `moveRightIcon` and `hideColumnIcon`. They default to the CoreUI icon set. The default menu's items depend on which features are on ([sorting](https://coreui.io/data-grid/react/docs/features/sorting/), [pinning](https://coreui.io/data-grid/react/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/)). Its labels come from [`labels`](https://coreui.io/data-grid/react/docs/customization/localization/); see [Accessibility](https://coreui.io/data-grid/react/docs/guides/accessibility/) for the keyboard model. --- # React Data Grid Styling & Theming > Theme the React Data Grid with --cui-data-grid-* CSS variables that resolve through CoreUI semantic tokens, so light and dark modes work with no extra CSS. The Data Grid is styled entirely with CSS custom properties. Every knob is a `--cui-data-grid-*` variable that resolves through CoreUI's semantic tokens (`--cui-body-bg`, `--cui-border-color`, …), so the grid inherits your theme — including `data-coreui-theme="dark"` — with no extra rules. ## Light & dark mode Because the tokens resolve through CoreUI semantic variables, dark mode works out of the box: ```tsx
``` No grid-specific dark styles are needed — the semantic tokens flip and the grid follows. ## Overriding tokens Set any variable on the grid element (or an ancestor) to retheme it: ```css .my-grid { --cui-data-grid-spacing: 0.75rem; --cui-data-grid-header-bg: var(--cui-tertiary-bg); --cui-data-grid-viewport-max-height: 40rem; } ``` ```tsx ``` ## CSS variables | Variable | Default | Controls | | --- | --- | --- | | `--cui-data-grid-viewport-max-height` | `30rem` | Max height of the scroll viewport. | | `--cui-data-grid-header-bg` | `var(--cui-body-bg, #fff)` | Header cell background. | | `--cui-data-grid-header-shadow` | `inset 0 calc(-1 * var(--cui-border-width, 1px)) 0 var(--cui-border-color, #dee2e6)` | Header bottom border. | | `--cui-data-grid-select-cell-width` | `2.5rem` | Width of the selection checkbox column. | | `--cui-data-grid-sorter-margin` | `.25rem` | Gap before the sort indicator. | | `--cui-data-grid-sorter-opacity` | `.3` | Resting opacity of the sort indicator. | | `--cui-data-grid-spacing` | `.5rem` | Cell padding. | | `--cui-data-grid-resizer-width` | `.625rem` | Hit area of the resize handle. | | `--cui-data-grid-resizer-grip-height` | `60%` | Height of the resize grip. | | `--cui-data-grid-resizer-grip-color` | `var(--cui-border-color, #dee2e6)` | Resize grip color. | | `--cui-data-grid-resizer-grip-color-hover` | `var(--cui-secondary-color, #6c757d)` | Resize grip color on hover. | | `--cui-data-grid-pinned-shadow-width` | `.375rem` | Width of the shadow cast by pinned columns. | | `--cui-data-grid-pinned-shadow` | `rgba(0, 0, 0, .15)` | Color of the pinned-column shadow. | | `--cui-data-grid-drop-indicator` | `var(--cui-primary, #321fdb)` | Drop indicator while reordering columns. | | `--cui-data-grid-loading-bg` | — | Background of the loading overlay (server-side mode). | | `--cui-data-grid-menu-bg` | `var(--cui-body-bg, #fff)` | Column menu background. | | `--cui-data-grid-menu-border-color` | `var(--cui-border-color, #dee2e6)` | Column menu border. | | `--cui-data-grid-menu-shadow` | `var(--cui-box-shadow, 0 .5rem 1rem rgba(0, 0, 0, .15))` | Column menu shadow. | | `--cui-data-grid-menu-min-width` | `10rem` | Column menu minimum width. | | `--cui-data-grid-menu-item-hover-bg` | `var(--cui-tertiary-bg, #f8f9fa)` | Column menu item hover background. | The grid also inherits the `.table` styles (striping, borders) from `@coreui/coreui(-pro)`, so table-level CSS variables like `--cui-table-striped-bg` apply too. --- # React Data Grid Localization > Translate every React Data Grid UI string through the labels prop, with {token} interpolation for dynamic values. Every UI string the grid renders — menu items, pagination labels, ARIA announcements — comes from the `labels` prop. Pass your own strings and they're merged over the defaults, so you only override what you need. ## Usage ```tsx ``` ## Interpolation Labels with `{token}` placeholders are interpolated at render time — unknown tokens are left untouched. Available tokens: `{column}` (column label), `{first}`, `{last}`, `{total}` (pagination range) and `{count}` (results announcement). ## Default labels | Key | Default | Used by | | --- | --- | --- | | `addCondition` | `Add condition` | [Filter menu](https://coreui.io/data-grid/react/docs/features/filtering/) | | `applyFilter` | `Apply` | [Filter menu](https://coreui.io/data-grid/react/docs/features/filtering/) | | `clearFilter` | `Clear filter` | [Filter menu](https://coreui.io/data-grid/react/docs/features/filtering/) + quick-input clear | | `clearSort` | `Unsort` | [Column menu](https://coreui.io/data-grid/react/docs/columns/menu/) sort actions | | `columnMenu` | `Column options for {column}` | [Column menu](https://coreui.io/data-grid/react/docs/columns/menu/) button | | `filterAction` | `Filter…` | [Column menu](https://coreui.io/data-grid/react/docs/columns/menu/) action | | `filterColumn` | `Filter {column}` | [Filter](https://coreui.io/data-grid/react/docs/features/filtering/) input | | `filterSummaryConditions` | `{count} conditions` | Quick-input summary | | `filterSummarySelected` | `{count} selected` | Quick-input summary | | `firstPage` | `First page` | [Pagination](https://coreui.io/data-grid/react/docs/features/pagination/) | | `globalFilterLabel` | `Search` | Global search accessible label | | `globalFilterPlaceholder` | `Search` | Global search placeholder | | `hideColumn` | `Hide column` | Column menu | | `itemsInfo` | `{first}–{last} of {total}` | Pagination range summary | | `joinAnd` | `AND` | [Filter menu](https://coreui.io/data-grid/react/docs/features/filtering/) | | `joinOr` | `OR` | [Filter menu](https://coreui.io/data-grid/react/docs/features/filtering/) | | `lastPage` | `Last page` | Pagination | | `loadError` | `Failed to load data` | [Server-side](https://coreui.io/data-grid/react/docs/features/server-side-data/) error state | | `loading` | `Loading…` | Server-side loading overlay | | `moveLeft` | `Move left` | Column menu | | `moveRight` | `Move right` | Column menu | | `nextPage` | `Next page` | Pagination | | `operatorBetween` | `Between` | Filter menu operators | | `operatorBlank` | `Blank` | Filter menu operators | | `operatorContains` | `Contains` | Filter menu operators | | `operatorEndsWith` | `Ends with` | Filter menu operators | | `operatorEquals` | `Equals` | Filter menu operators | | `operatorGreaterThan` | `Greater than` | Filter menu operators | | `operatorGreaterThanOrEqual` | `Greater than or equal` | Filter menu operators | | `operatorLessThan` | `Less than` | Filter menu operators | | `operatorLessThanOrEqual` | `Less than or equal` | Filter menu operators | | `operatorNotBlank` | `Not blank` | Filter menu operators | | `operatorNotContains` | `Does not contain` | Filter menu operators | | `operatorNotEquals` | `Not equals` | Filter menu operators | | `operatorStartsWith` | `Starts with` | Filter menu operators | | `pageSizeLabel` | `Rows per page` | Pagination page-size selector | | `paginationLabel` | `Pagination` | Pagination nav accessible label | | `pinLeft` | `Pin left` | Column menu | | `pinRight` | `Pin right` | Column menu | | `previousPage` | `Previous page` | Pagination | | `redoneAnnouncement` | `Change redone` | [Undo & redo](https://coreui.io/data-grid/react/docs/features/history/) ARIA live announcement | | `resetColumns` | `Reset` | [Toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/) column chooser | | `resultsAnnouncement` | `{count} results` | ARIA live announcement | | `searchValues` | `Search values` | Set filter search box | | `selectAllRows` | `Select all rows` | [Selection](https://coreui.io/data-grid/react/docs/features/row-selection/) header checkbox | | `selectAllValues` | `Select all` | Set filter | | `selectRow` | `Select row` | Selection row checkbox | | `showAllColumns` | `Show all` | [Toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/) column chooser | | `sortAscending` | `Sort ascending` | [Column menu](https://coreui.io/data-grid/react/docs/columns/menu/) sort actions | | `sortDescending` | `Sort descending` | [Column menu](https://coreui.io/data-grid/react/docs/columns/menu/) sort actions | | `toolbarColumns` | `Columns` | [Toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/) columns button | | `toolbarExport` | `Export` | [Toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/) export button | | `toolbarRedo` | `Redo` | [Toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/) redo button | | `toolbarUndo` | `Undo` | [Toolbar](https://coreui.io/data-grid/react/docs/features/toolbar/) undo button | | `undoneAnnouncement` | `Change undone` | [Undo & redo](https://coreui.io/data-grid/react/docs/features/history/) ARIA live announcement | | `unpin` | `Unpin` | Column menu | The defaults are exported as `DEFAULT_LABELS` from `@coreui/react-data-grid` if you want to extend rather than replace them. --- # React Data Grid Accessibility > How the React Data Grid supports keyboard interaction, ARIA roles and screen-reader announcements, and where the current limits are. The Data Grid ships accessible controls for its interactive chrome and exposes every string for translation. This page documents what's supported today and what's still on the [roadmap](https://coreui.io/data-grid/react/docs/resources/roadmap/). ## Grid keyboard navigation With [`cellNavigation`](https://coreui.io/data-grid/react/docs/features/keyboard-navigation/) on, the grid implements the full ARIA [grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/): `role="grid"`, `aria-colcount`/`aria-colindex` alongside the absolute `aria-rowcount`/`aria-rowindex`, a single tab stop with a roving-tabindex active cell, and arrow-key movement across header and data cells. The option is opt-in — claiming `role="grid"` without the full keyboard contract would be worse than the native table semantics the grid keeps by default. ## Column menu The [column menu](https://coreui.io/data-grid/react/docs/columns/menu/) follows the ARIA menu pattern: - **Arrow keys** move between items; **Home/End** jump to the first/last item. - **Escape** closes the menu and restores focus to the ⋮ trigger. - **Move left / Move right** give a keyboard-accessible alternative to drag-and-drop [column reordering](https://coreui.io/data-grid/react/docs/columns/ordering-visibility/). All menu labels come from [`labels`](https://coreui.io/data-grid/react/docs/customization/localization/), so the menu is fully translatable. ## Selection The [selection](https://coreui.io/data-grid/react/docs/features/row-selection/) checkboxes are real form controls with accessible labels (`selectRow`, `selectAllRows`) — reachable and toggleable by keyboard. ## Server-side loading In [server-side mode](https://coreui.io/data-grid/react/docs/features/server-side-data/) the grid sets `aria-busy` while a request is in flight and exposes the `loading` label, so assistive tech announces the pending state. ## Live announcements Result counts are announced through an ARIA live region using the `resultsAnnouncement` label (`{count} results`) as filters change. ## Current limits `cellNavigation` (and the [inline editing](https://coreui.io/data-grid/react/docs/features/editing/) built on it) is opt-in in this release. Screen-reader-verified defaults — flipping `role="grid"` on out of the box — are a separate decision planned after NVDA and VoiceOver testing. --- # React Data Grid Performance > How the React Data Grid stays fast at 100,000 rows, the props that tune rendering, and when to move paging to your backend. The Data Grid is built to stay responsive on large datasets. This page explains how it does that and the levers you have when you need to tune it. ## Why it's fast - **Row virtualization.** Only the rows in the scroll viewport (plus a buffer) exist in the DOM — see [Virtualization](https://coreui.io/data-grid/react/docs/features/virtualization/). A 100,000-row grid renders a few dozen `` elements, not 100,000. - **Cheap cell values.** Column [`formatter`](https://coreui.io/data-grid/react/docs/columns/overview/) runs on the scroll hot path and returns a string, so it stays cheap. Reserve `render` (which builds JSX) for the columns that truly need interactive content. - **Debounced, race-safe fetches.** In [server-side mode](https://coreui.io/data-grid/react/docs/features/server-side-data/), rapid state changes coalesce into one request and stale responses are dropped. ## Tuning levers | Lever | Effect | | --- | --- | | `rowHeight` | The estimated row height (default `44`). Set it close to your real height so the virtualizer sizes the scroll area accurately. | | `overscan` | Extra rows rendered above/below the viewport (default `10`). Raise it if you see blank rows during fast scrolling; lower it to shave DOM nodes. | | `formatter` vs `render` | Prefer `formatter` for values on the hot path; `render` allocates elements per visible cell. | | Stable props | Memoize `items` and `columns` (e.g. `useMemo`) so the grid doesn't recompute row models on every parent render. | ## When to go server-side Client-side mode keeps the whole dataset in memory. That's fine for tens of thousands of rows, but past what the browser can hold — or when the data lives in a database you don't want to ship whole — move sorting, filtering and paging to your API with [server-side data](https://coreui.io/data-grid/react/docs/features/server-side-data/). The grid then holds only the current page. ## Rules of thumb - A few thousand to ~100k rows in the browser → client-side with virtualization. - Beyond that, or data behind an API → server-side mode. - Keep `formatter` pure and cheap; it runs for every visible cell on every render. --- # React Data Grid Options > Full reference of CDataGrid props — columns, data, features and behavior of the React Data Grid. Pass props to the component: ``. There is no imperative update call — change a prop and the grid re-renders. ### CDataGrid ```jsx import { CDataGrid } from '@coreui/react-data-grid' ``` ### Props | Name | Type | Default | Description | | --- | --- | --- | --- | | `cellNavigation` | `boolean \| undefined` | - | APG grid keyboard navigation — `role="grid"`, a roving-tabindex active cell, and arrow-key movement across header and data cells. | | `columnFilters` | `boolean \| undefined` | - | Renders a filter row in the header with an input per filterable column. | | `columnMenu` | `boolean \| CDataGridColumnMenuBuilder \| undefined` | - | Adds a per-column header menu — `true` builds it from the enabled features (sort/pin/move/hide); a builder `({ column, defaultActions }) => actions` returns the final item list. | | `columnMenuIcon` | `ReactNode` | - | Custom column menu button icon (ReactNode) replacing the default one. | | `columnOrder` | `boolean \| ColumnOrderState \| undefined` | - | Enables drag-and-drop column reordering; an array sets the initial order. | | `columnPinning` | `boolean \| ColumnPinningState \| undefined` | - | Freezes columns (by `key`) to the left/right edge with sticky positioning; `true` enables the feature with no initial pins. | | `columns` | `CDataGridColumn[]` | - | Column definitions. | | `columnSizing` | `boolean \| DataGridColumnSizingOptions \| undefined` | - | Enables column resize handles; `mode` controls whether widths update while dragging (`'onChange'`, the default) or on release (`'onEnd'`). | | `columnVisibility` | `boolean \| VisibilityState \| undefined` | - | Enables hiding/showing columns via `column.toggleVisibility()`; an object sets the initial visibility. | | `dataProvider` | `((request: DataGridDataRequest) => Promise) \| undefined` | - | Server-side mode: the grid requests data on every sorting/filter/page change; implies pagination. | | `editing` | `boolean \| undefined` | - | Inline cell editing on Enter/F2/double-click for columns opting in via `editable`/`editor`; implies `cellNavigation`. | | `empty` | `ReactNode` | - | Content (ReactNode) shown when no rows match. | | `filterIcon` | `ReactNode` | - | Icon for the per-column filter button. | | `globalFilter` | `boolean \| undefined` | - | Renders a search input above the grid that filters across all columns. | | `hideColumnIcon` | `ReactNode` | - | Custom hide-column menu action icon (ReactNode) replacing the default one. | | `history` | `boolean \| undefined` | - | Undo/redo history of edit commits — Ctrl/Cmd+Z and Ctrl+Shift+Z/Ctrl+Y plus toolbar buttons; requires `editing`. | | `itemKey` | `((item: DataGridItem, index: number) => string) \| undefined` | - | Stable row id — required for selection to survive sorting and filtering. | | `items` | `DataGridItem[] \| undefined` | - | Row data. | | `labels` | `Partial \| undefined` | - | UI strings (i18n), merged over the defaults; supports `{token}` interpolation. | | `moveLeftIcon` | `ReactNode` | - | Custom move-left menu action icon (ReactNode) replacing the default one. | | `moveRightIcon` | `ReactNode` | - | Custom move-right menu action icon (ReactNode) replacing the default one. | | `onDataError` | `((error: unknown) => void) \| undefined` | - | Fires when the data provider rejects, with the error (server-side mode). | | `onDataLoad` | `((response: DataGridDataResponse) => void) \| undefined` | - | Fires when the data provider resolves, with the `{ items, totalRows }` response (server-side mode). | | `onEditCancel` | `((item: DataGridItem, columnId: string) => void) \| undefined` | - | Fires when an edit is cancelled, with the item and column key. | | `onEditCommit` | `((event: DataGridEditCommitEvent) => void) \| undefined` | - | Fires when an edit commits, with `{ item, columnId, value, previousValue }` — the grid never mutates `items`; apply the change and re-render. | | `onEditStart` | `((item: DataGridItem, columnId: string) => void) \| undefined` | - | Fires when a cell enters edit mode, with the item and column key. | | `onFilterChange` | `((columnFilters: ColumnFiltersState, globalFilter: string) => void) \| undefined` | - | Fires when column filters or the global filter change, with both filter states. | | `onOrderChange` | `((columnOrder: ColumnOrderState) => void) \| undefined` | - | Fires when columns are reordered, with the TanStack columnOrder state. | | `onPaginationChange` | `((pagination: PaginationState) => void) \| undefined` | - | Fires when the page or page size changes, with the TanStack pagination state. | | `onPinningChange` | `((columnPinning: ColumnPinningState) => void) \| undefined` | - | Fires when column pinning changes, with the TanStack columnPinning state. | | `onSelectionChange` | `((rowSelection: RowSelectionState, selectedItems: DataGridItem[]) => void) \| undefined` | - | Fires when the row selection changes, with the TanStack rowSelection state and the selected items. | | `onSizingChange` | `((columnSizing: ColumnSizingState) => void) \| undefined` | - | Fires when a column is resized, with the TanStack columnSizing state. | | `onSortingChange` | `((sorting: SortingState) => void) \| undefined` | - | Fires when sorting changes, with the TanStack sorting state. | | `onVisibilityChange` | `((columnVisibility: VisibilityState) => void) \| undefined` | - | Fires when column visibility changes, with the TanStack columnVisibility state. | | `overscan` | `number \| undefined` | - | Extra rows rendered above and below the visible window. | | `pagination` | `boolean \| DataGridPaginationOptions \| undefined` | - | Pagination mode — mutually exclusive with virtualization. | | `pinLeftIcon` | `ReactNode` | - | Custom pin-left menu action icon (ReactNode) replacing the default one. | | `pinRightIcon` | `ReactNode` | - | Custom pin-right menu action icon (ReactNode) replacing the default one. | | `rowHeight` | `number \| undefined` | - | Estimated row height in px used by the virtualizer. | | `rowSelection` | `boolean \| DataGridRowSelectionOptions \| undefined` | - | Checkbox selection with select-all and shift+click ranges. | | `slots` | `Partial \| undefined` | - | Replaces built-in chrome — each slot is a component rendered with `{ table, labels }`. | | `sortAscendingIcon` | `ReactNode` | - | Custom ascending sort indicator icon (ReactNode) replacing the default one. | | `sortDescendingIcon` | `ReactNode` | - | Custom descending sort indicator icon (ReactNode) replacing the default one. | | `sorterVisibility` | `"always" \| "hover" \| undefined` | - | Controls when the neutral (unsorted) sort icon is shown on sortable columns: `'always'` keeps it visible, `'hover'` reveals it on header hover/focus. Omit to hide it until the column is sorted. | | `sorting` | `boolean \| DataGridSortingOptions \| undefined` | - | Column sorting; shift+click adds columns to the sort. | | `sortNeutralIcon` | `ReactNode` | - | Custom unsorted sort indicator icon (ReactNode) replacing the default one. | | `stateKey` | `string \| undefined` | - | Persists the grid state (sorting, filters, order, sizing, visibility, pinning, selection, page) to localStorage under this key — autosaved on every change and restored on mount. | | `tableRef` | `Ref> \| undefined` | - | Ref that receives the TanStack table instance. | | `toolbar` | `boolean \| CDataGridToolbarOptions \| undefined` | - | Adds a built-in toolbar above the grid — `true` enables every action whose feature is on (column chooser, CSV export, global search); an object picks each individually. | | `toolbarColumnsIcon` | `ReactNode` | - | Custom toolbar column-chooser button icon (ReactNode) replacing the default one. | | `toolbarExportIcon` | `ReactNode` | - | Custom toolbar export button icon (ReactNode) replacing the default one. | | `toolbarRedoIcon` | `ReactNode` | - | Custom toolbar redo button icon (ReactNode) replacing the default one. | | `toolbarUndoIcon` | `ReactNode` | - | Custom toolbar undo button icon (ReactNode) replacing the default one. | | `unpinIcon` | `ReactNode` | - | Custom unpin menu action icon (ReactNode) replacing the default one. | | `virtualization` | `boolean \| undefined` | - | Windowed rendering for large datasets. | --- # React Data Grid Column API > Full reference of React Data Grid column definition keys — labels, sorting, filtering, formatting and custom rendering. Each entry in the [`columns`](https://coreui.io/data-grid/react/docs/api/options/) prop describes one column. `key` is the only required field. See [Columns overview](https://coreui.io/data-grid/react/docs/columns/overview/) for a guided tour. | Key | Type | Description | | --- | --- | --- | | `key` | `string` | Property name in the item object (also the column id). | | `label` | `string` | Header label; falls back to `key`. | | `editable` | `boolean \| { type?, min?, max?, step?, options? }` | Opts the column into [inline editing](https://coreui.io/data-grid/react/docs/features/editing/) with a built-in `text`, `number` or `select` editor. | | `editor` | `ComponentType` | Custom editor component — renders with `{ item, column, value, invalid, labels, commit, cancel, handleRef }`. Its presence opts the column in. | | `editorPopup` | `boolean` | Renders the editor in an overlay anchored to the cell instead of inline — for rich editors whose UI extends beyond the cell. | | `editValidate` | `(value, item) => true \| string` | Gates the commit — a returned message blocks it and marks the editor invalid. | | `sortable` | `boolean` | Set `false` to disable sorting for this column. | | `filterable` | `boolean` | Set `false` to remove the column's filter button. | | `filter` | `ComponentType<{ column, table, labels }>` | Custom filter component replacing the default text input (requires `columnFilters`). | | `filterFn` | `(value, filterValue, item) => boolean` | Custom predicate replacing the default case-insensitive contains. | | `filterType` | `'text' \| 'number' \| 'date' \| 'select'` | Operator set for the built-in [filter menu](https://coreui.io/data-grid/react/docs/features/filtering/); `select` renders the faceted set filter. Defaults to `text`. | | `resizable` | `boolean` | Set `false` to drop the resize handle for this column (when `columnSizing` is on). | | `movable` | `boolean` | Set `false` to exclude the column from drag-and-drop reordering. | | `hideable` | `boolean` | Set `false` to prevent hiding the column. | | `formatter` | `(value, item) => string` | Formats the cell value — cheap, stays on the scroll hot path. | | `render` | `(item, index) => ReactNode` | Full custom cell content (e.g. action buttons) — returns JSX. | | `width` | `number` | Initial column width in pixels - seeds `columnSizing` and drives the layout. | | `style` | `CSSProperties` | Inline styles for the header cell (cosmetic; e.g. percentage widths are visual-only). | `formatter` runs on the scroll hot path and its output is used for [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/); `render` is for rich cell content and is never exported. Use one or the other per column. --- # React Data Grid Events > React Data Grid callback props — onXxx handlers, each receiving structured state as plain arguments. Every state change calls the matching `onXxx` prop with structured state as plain arguments: ```tsx console.log(sorting)} /> ``` | Prop | Payload | | --- | --- | | `onSortingChange` | `sorting: SortingState` | | `onFilterChange` | `columnFilters: ColumnFiltersState, globalFilter: string` | | `onSelectionChange` | `rowSelection: RowSelectionState, selectedItems: object[]` | | `onPaginationChange` | `pagination: PaginationState` | | `onEditStart` | `item: object, columnId: string` | | `onEditCommit` | `{ item, columnId, value, previousValue }` — the grid never mutates `items`; apply the change yourself. [Undo/redo](https://coreui.io/data-grid/react/docs/features/history/) re-calls it with the values swapped | | `onEditCancel` | `item: object, columnId: string` | | `onSizingChange` | `columnSizing: ColumnSizingState` | | `onPinningChange` | `columnPinning: ColumnPinningState` | | `onOrderChange` | `columnOrder: ColumnOrderState` | | `onVisibilityChange` | `columnVisibility: VisibilityState` | | `onDataLoad` | `{ items, totalRows }` (server-side mode) | | `onDataError` | `error: unknown` (server-side mode) | The state types (`SortingState`, `ColumnFiltersState`, …) are exported from `@coreui/react-data-grid`. Callbacks fire for changes driven through the [headless table](https://coreui.io/data-grid/react/docs/api/headless/) too. --- # React Data Grid Methods > React Data Grid helpers — CSV export functions, the default labels export and the tableRef escape hatch. `` has no imperative instance methods — React re-renders the grid when a prop changes, and unmounting cleans it up. What the vanilla grid exposes as methods maps to exports and props instead: | Export / prop | Description | | --- | --- | | `exportCsv(table, options?)` | Returns the rows as an RFC-4180 CSV string. Options: `scope`, `delimiter`, `bom`, `sanitize`. See [CSV export](https://coreui.io/data-grid/react/docs/features/csv-export/). | | `downloadCsv(table, options?)` | Downloads the CSV as a file. Same options plus `filename`. | | `DEFAULT_LABELS` | The default UI strings — extend rather than replace them. See [Localization](https://coreui.io/data-grid/react/docs/customization/localization/). | | `tableRef` (prop) | A ref to the underlying headless table instance — the [headless escape hatch](https://coreui.io/data-grid/react/docs/api/headless/) for building custom UI. | ```tsx import { CDataGrid, downloadCsv } from '@coreui/react-data-grid' const tableRef = useRef[0]>(null) // later, e.g. in a button handler: tableRef.current && downloadCsv(tableRef.current, { filename: 'users.csv' }) ``` Selected rows arrive through the `onSelectionChange` callback (its `selectedItems` argument) — see [Row selection](https://coreui.io/data-grid/react/docs/features/row-selection/). To refresh data, pass a new `items` array; the grid re-renders on prop change. --- # React Data Grid Headless Table > Drop down to the underlying headless table instance via tableRef to build custom React Data Grid UI and drive state imperatively. The Data Grid is a thin, styled layer over a headless table engine. When the built-in chrome isn't enough, turn it off and drive the grid from your own UI through the `tableRef` prop — the same instance the grid renders from. ```tsx import { CDataGrid } from '@coreui/react-data-grid' import { useRef } from 'react' const tableRef = useRef(null) // Drive state imperatively through the underlying table instance: tableRef.current?.setPageIndex(3) tableRef.current?.getFilteredRowModel() tableRef.current?.setColumnPinning({ left: ['name'] }) tableRef.current?.getState().sorting ``` ## When to reach for it - **Custom chrome.** Build your own toolbar, pager or column chooser and wire it to `tableRef.current`. The [slots](https://coreui.io/data-grid/react/docs/features/slots/) API hands you the same `table` scoped to a mount point — prefer slots when you only need to replace one module. - **Reading state.** `tableRef.current?.getState()` exposes sorting, filters, selection, pagination, pinning, order and visibility as structured state. - **Imperative actions.** Set the page, toggle a column, change pinning or apply a filter without waiting for user interaction. ## Notes Everything the built-in UI does routes through this same table, so your imperative calls and the built-in controls stay in sync. The grid's [callbacks](https://coreui.io/data-grid/react/docs/api/events/) fire for headless-driven changes too. --- # React Data Grid Roadmap > Features planned for future CoreUI Data Grid releases — inline editing, grouping and aggregation, tree data, full grid keyboard navigation and dynamic row heights. The Data Grid is under active development. These capabilities are planned for future releases; the pages in this documentation cover what ships today. ## Shipped - **[Inline editing](https://coreui.io/data-grid/react/docs/features/editing/)** — edit cell values in place, with built-in editors and a popup contract for rich ones. - **[Full keyboard navigation](https://coreui.io/data-grid/react/docs/features/keyboard-navigation/)** — `role="grid"` cell-by-cell arrow-key movement, extending the [accessible chrome](https://coreui.io/data-grid/react/docs/guides/accessibility/). ## Planned - **Grouping & aggregation** — group rows and compute totals per group. - **Tree data** — hierarchical, expandable rows. - **Dynamic row heights** — per-row heights in the virtualizer, beyond today's fixed `rowHeight`. Have a request or want to track progress? Open an issue on the [CoreUI Data Grid repository](https://github.com/coreui/coreui-data-grid).