# CoreUI Data Grid JavaScript documentation > High-performance data grid for CoreUI — 100,000 rows with sorting, filtering, selection and pagination. --- # CoreUI Data Grid > High-performance data grid — 100,000 rows with sorting, filtering, selection, pagination, server-side data, column resizing, pinning, ordering and full theming, around 48 KB gzipped. 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/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/docs/api/headless/) any time. - **Complete feature set.** Sorting, [filtering](https://coreui.io/data-grid/docs/features/filtering/), [selection](https://coreui.io/data-grid/docs/features/row-selection/), [pagination](https://coreui.io/data-grid/docs/features/pagination/), [server-side data](https://coreui.io/data-grid/docs/features/server-side-data/), column [sizing](https://coreui.io/data-grid/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/), a [column menu](https://coreui.io/data-grid/docs/columns/menu/) and [CSV export](https://coreui.io/data-grid/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/docs/customization/styling/). - **Small.** Around 48 KB gzipped for the vanilla build. ## How it's built The grid is a thin, styled layer over a headless table engine (state, sorting, filtering, pagination, pinning, ordering, visibility) and row virtualization (windowed rendering). Options are named after the features they control, events are verbs, and event payloads carry the grid's own state — a small, predictable API. ## Get started 1. [Install](https://coreui.io/data-grid/docs/getting-started/installation/) the package. 2. Follow the [Quickstart](https://coreui.io/data-grid/docs/getting-started/quickstart/) to render your first grid. 3. Browse the [feature matrix](https://coreui.io/data-grid/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/) | --- # Data Grid Installation > Install CoreUI Data Grid via npm and load its stylesheet, or drop in the UMD bundle. ## npm ```sh npm install @coreui/data-grid ``` Data Grid ships its own stylesheet 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. ```js import { DataGrid } from '@coreui/data-grid' import '@coreui/data-grid/dist/css/data-grid.css' ``` ## UMD bundle Without a bundler, include the script and stylesheet and use the global `coreui.DataGrid`: ```html ``` ## 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/docs/customization/styling/) for the full token reference. Next: the [Quickstart](https://coreui.io/data-grid/docs/getting-started/quickstart/). --- # Data Grid Quickstart > Render your first CoreUI Data Grid 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/docs/getting-started/installation/) `@coreui/data-grid` and loaded its stylesheet. ## 1. A container The grid renders into any element: ```html
``` ## 2. Columns and data Define columns by `key` (the property to read from each item) and pass your `items`: ```js import { DataGrid } from '@coreui/data-grid' import '@coreui/data-grid/dist/css/data-grid.css' const items = [ { id: 1, name: 'Alice', role: 'admin' }, { id: 2, name: 'Bob', role: 'editor' }, { id: 3, name: 'Carol', role: 'viewer' }, ] const grid = new DataGrid(document.getElementById('grid'), { columns: [ { key: 'name', label: 'Name' }, { key: 'role', label: 'Role' }, ], items, itemKey: (item) => String(item.id), }) ``` `itemKey` returns a stable id per row. It's optional, but [selection](https://coreui.io/data-grid/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 option. Add filtering and selection: ```js const grid = new DataGrid(element, { columns, items, itemKey: (item) => String(item.id), columnFilters: true, // per-column filter row rowSelection: true, // checkbox column with select-all }) ``` Sorting is on by default. From here, explore the [feature matrix](https://coreui.io/data-grid/docs/getting-started/features/) or jump to any feature page. ## 4. React to changes The grid emits namespaced [events](https://coreui.io/data-grid/docs/api/events/) with structured state: ```js element.addEventListener('selectionChange.coreui.data-grid', (event) => { console.log(event.selectedItems) }) ``` ## What's next - Handle large or remote data with [server-side data](https://coreui.io/data-grid/docs/features/server-side-data/). - Customize cells with a column [`formatter` or `render`](https://coreui.io/data-grid/docs/columns/overview/). - Replace built-in chrome with [slots](https://coreui.io/data-grid/docs/features/slots/) or drive the [headless table](https://coreui.io/data-grid/docs/api/headless/) directly. --- # Data Grid Features > A capability matrix of everything CoreUI Data Grid does today, with the option that turns each feature on and a link to its docs. Everything the Data Grid does today, the option that enables it, and where to read more. Features not listed here are on the [roadmap](https://coreui.io/data-grid/docs/resources/roadmap/). ## Data & rendering | Feature | Option | Docs | | --- | --- | --- | | Row virtualization | `virtualization` (on by default) | [Virtualization](https://coreui.io/data-grid/docs/features/virtualization/) | | Pagination | `pagination` | [Pagination](https://coreui.io/data-grid/docs/features/pagination/) | | Server-side data | `dataProvider` | [Server-side data](https://coreui.io/data-grid/docs/features/server-side-data/) | | Row selection | `rowSelection` | [Row selection](https://coreui.io/data-grid/docs/features/row-selection/) | ## Sorting & filtering | Feature | Option | Docs | | --- | --- | --- | | Column sorting (multi-column) | `sorting` (on by default) | [Sorting](https://coreui.io/data-grid/docs/features/sorting/) | | Per-column filter row | `columnFilters` | [Filtering](https://coreui.io/data-grid/docs/features/filtering/) | | Global search | `globalFilter` | [Filtering](https://coreui.io/data-grid/docs/features/filtering/) | | Custom filter UI / predicate | `filter`, `filterFn` (per column) | [Filtering](https://coreui.io/data-grid/docs/features/filtering/) | ## Columns | Feature | Option | Docs | | --- | --- | --- | | Custom cell formatting / rendering | `formatter`, `render` (per column) | [Columns overview](https://coreui.io/data-grid/docs/columns/overview/) | | Column resizing | `columnSizing` | [Column sizing](https://coreui.io/data-grid/docs/columns/sizing/) | | Column pinning | `columnPinning` | [Column pinning](https://coreui.io/data-grid/docs/columns/pinning/) | | Column ordering (drag & drop) | `columnOrder` | [Ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/) | | Column visibility | `columnVisibility` | [Ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/) | | Column header menu | `columnMenu` | [Column menu](https://coreui.io/data-grid/docs/columns/menu/) | ## Customization & output | Feature | Option / API | Docs | | --- | --- | --- | | Custom toolbar / pagination / empty state | `slots` | [Slots](https://coreui.io/data-grid/docs/features/slots/) | | CSV export | `getCsv()`, `downloadCsv()` | [CSV export](https://coreui.io/data-grid/docs/features/csv-export/) | | Theming (CSS variables) | `--cui-data-grid-*` | [Styling & theming](https://coreui.io/data-grid/docs/customization/styling/) | | Localization (i18n) | `labels` | [Localization](https://coreui.io/data-grid/docs/customization/localization/) | | Headless escape hatch | `grid.table` | [Headless table](https://coreui.io/data-grid/docs/api/headless/) | --- # LLMs.txt > LLM-optimized documentation endpoints for CoreUI 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 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/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/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/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/docs/features/sorting.md](https://coreui.io/data-grid/docs/features/sorting.md) --- # MCP Server > Bring the CoreUI 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 Data Grid docs with the `--base-path` option shown below. The `bootstrap` key selects the vanilla (JavaScript) edition. 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 bootstrap --base-path /data-grid/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", "bootstrap", "--base-path", "/data-grid/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", "bootstrap", "--base-path", "/data-grid/docs"] } } } ``` ### Windsurf Edit `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "coreui-data-grid": { "command": "npx", "args": ["-y", "@coreui/docs-mcp", "--framework", "bootstrap", "--base-path", "/data-grid/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", "bootstrap", "--base-path", "/data-grid/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 bootstrap --base-path /data-grid/docs ``` ```toml [mcp_servers.coreui-data-grid] command = "npx" args = ["-y", "@coreui/docs-mcp", "--framework", "bootstrap", "--base-path", "/data-grid/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 Data Grid?" - "What options does CoreUI Data Grid accept?" - "Show me the CoreUI Data Grid pagination documentation." - "How do I export CoreUI Data Grid data to CSV?" The package is open source and published as [`@coreui/docs-mcp`](https://www.npmjs.com/package/@coreui/docs-mcp). --- # Data Grid Overview > A single kitchen-sink CoreUI 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/docs/features/toolbar/), [per-column filters](https://coreui.io/data-grid/docs/features/filtering/), [column sizing](https://coreui.io/data-grid/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/), the [column menu](https://coreui.io/data-grid/docs/columns/menu/), [row selection](https://coreui.io/data-grid/docs/features/row-selection/), multi-column [sorting](https://coreui.io/data-grid/docs/features/sorting/) and [pagination](https://coreui.io/data-grid/docs/features/pagination/). Every one of these is a single option, documented on its own page — this demo just enables them together. ```html
``` ```js 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 = { active: 'success', invited: 'info', suspended: 'danger' } const items = 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))}` })) const currency = value => Number(value).toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }) const date = value => new Date(value).toLocaleDateString('en-US') new coreui.DataGrid(document.getElementById('dataGridOverview'), { columns: [ { key: 'id', label: '#', width: 72, hideable: false }, { key: 'name', label: 'Name', width: 180 }, { key: 'email', label: 'Email', width: 220 }, { key: 'department', label: 'Department', width: 150, filterType: 'select' }, { key: 'role', label: 'Role', width: 130, filterType: 'select' }, { key: 'status', label: 'Status', width: 130, filterType: 'select', render(item) { return `${item.status}` } }, { 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, itemKey: item => String(item.id), columnFilters: true, columnMenu: true, columnOrder: true, columnPinning: { left: ['id'] }, columnSizing: true, columnVisibility: { projects: false, city: false, lastActive: false, phone: false }, rowSelection: true, sorting: { multiple: true }, pagination: { pageSize: 20, pageSizeOptions: [10, 20, 50, 100] }, toolbar: { columns: true, export: { filename: 'employees.csv' }, search: true } }) ``` ## What's turned on | Option | Feature | | --- | --- | | `toolbar` | [Column chooser, CSV export and global search](https://coreui.io/data-grid/docs/features/toolbar/) | | `columnFilters` + `filterType` | [Per-column typed filters](https://coreui.io/data-grid/docs/features/filtering/) (text, number, date, select) | | `columnSizing` | [Drag-to-resize columns](https://coreui.io/data-grid/docs/columns/sizing/) | | `columnPinning` | [`id` pinned to the left edge](https://coreui.io/data-grid/docs/columns/pinning/) | | `columnOrder` | [Drag-and-drop column reordering](https://coreui.io/data-grid/docs/columns/ordering-visibility/) | | `columnVisibility` | [Four columns hidden until you show them](https://coreui.io/data-grid/docs/columns/ordering-visibility/) | | `columnMenu` | [Per-header sort / pin / hide menu](https://coreui.io/data-grid/docs/columns/menu/) | | `rowSelection` | [Checkbox column with select-all](https://coreui.io/data-grid/docs/features/row-selection/) | | `sorting: { multiple: true }` | [Shift-click multi-column sort](https://coreui.io/data-grid/docs/features/sorting/) | | `pagination` | [Page size switcher](https://coreui.io/data-grid/docs/features/pagination/) | ## Presentation `salary` and the two dates use a column [`formatter`](https://coreui.io/data-grid/docs/columns/overview/) so the displayed value — and the [CSV export](https://coreui.io/data-grid/docs/features/csv-export/) — reads as currency and localized dates. The `status` column uses [`render`](https://coreui.io/data-grid/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/docs/features/server-side-data/). --- # Data Grid Virtualization > Row virtualization renders only the visible window of rows, so the 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: true`) and is mutually exclusive with [pagination](https://coreui.io/data-grid/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

``` ```js const element = document.getElementById('dataGridVirtual') 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'] const items = Array.from({ length: 100000 }, (_, 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 } }) const start = performance.now() new coreui.DataGrid(element, { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name', width: 200 }, { key: 'email', label: 'Email', width: 260 }, { key: 'role', label: 'Role', width: 110 }, { key: 'status', label: 'Status', width: 110 }, { key: 'score', label: 'Score', width: 90 } ], items, itemKey: item => String(item.id), columnFilters: true, globalFilter: true, rowSelection: true }) const meta = document.getElementById('dataGridVirtualMeta') if (meta) { meta.textContent = `100,000 rows initialized in ${Math.round(performance.now() - start)} ms` } ``` ## How it works The grid measures the scroll viewport and renders only the rows that intersect it. Two options 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/docs/resources/roadmap/). For datasets larger than browser memory, hand paging to your backend with [server-side data](https://coreui.io/data-grid/docs/features/server-side-data/). See the [Performance guide](https://coreui.io/data-grid/docs/guides/performance/) for tuning advice. --- # Data Grid Sorting > Sort the 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
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, role: roles[i % roles.length], score: (i * 37) % 1000 })) new coreui.DataGrid(document.getElementById('dataGridSorting'), { columns: [ { key: 'id', label: '#', width: 90, sortable: false }, { key: 'name', label: 'Name' }, { key: 'role', label: 'Role', width: 140 }, { key: 'score', label: 'Score', width: 120 } ], items, itemKey: item => String(item.id), sorting: { multiple: true }, // shift+click a header to add a column to the sort pagination: { pageSize: 10 } }) ``` ## Usage ```js new coreui.DataGrid(element, { columns, items, sorting: true, // the default — pass an object to configure it }) ``` 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/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. | ```js new coreui.DataGrid(element, { columns, items, sorterVisibility: 'hover', }) ``` ## 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
n

Click a header, then shift+click another to sort by multiple columns.

``` ```js const departments = ['Engineering', 'Design', 'Sales'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, department: departments[i % departments.length], salary: 40000 + ((i * 137) % 60000) })) const labels = { name: 'Name', department: 'Department', salary: 'Salary' } const element = document.getElementById('dataGridSortingMulti') const status = document.getElementById('dataGridSortingMultiStatus') element.addEventListener('sortingChange.coreui.data-grid', event => { status.textContent = event.sorting.length ? `Sorted by: ${event.sorting .map((sort, index) => `${index + 1}. ${labels[sort.id]} ${sort.desc ? '↓' : '↑'}`) .join(' ')}` : 'Click a header, then shift+click another to sort by multiple columns.' }) new coreui.DataGrid(element, { columns: [ { key: 'id', label: '#', width: 90, sortable: false }, { key: 'name', label: 'Name' }, { key: 'department', label: 'Department', width: 160 }, { key: 'salary', label: 'Salary', width: 140 } ], items, itemKey: item => String(item.id), sorting: { multiple: true }, // shift+click a second header to add it to the sort pagination: { pageSize: 10 } }) ``` ## Reacting to sort changes Each change emits `sortingChange.coreui.data-grid` with the grid's `{ sorting }` state: ```js element.addEventListener('sortingChange.coreui.data-grid', (event) => { console.log(event.sorting) // [{ id: 'name', desc: false }] }) ``` In [server-side mode](https://coreui.io/data-grid/docs/features/server-side-data/) the same `sorting` state is handed to your `dataProvider` so your API does the ordering. --- # Data Grid Filtering > Filter the Data Grid with per-column filter dialogs (typed operators, AND/OR, set filter), a global search input, custom filter UIs and custom matching predicates. The Data Grid filters on two levels. `columnFilters: true` adds a filter button with a dialog to every filterable column header; `globalFilter: true` adds a single search input above the grid that matches across every column (the same input as the [toolbar](https://coreui.io/data-grid/docs/features/toolbar/)'s `search` action). Both narrow the rows client-side (or feed your [`dataProvider`](https://coreui.io/data-grid/docs/features/server-side-data/) in server-side mode). Opt a column out with `filterable: false`. ```js new coreui.DataGrid(element, { columns, items, columnFilters: true, // per-column filter buttons + dialog globalFilter: true, // single cross-column search input }) ``` ## 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/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
``` ```js const departments = ['Engineering', 'Design', 'Sales', 'Support'] const items = 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) })) new coreui.DataGrid(document.getElementById('dataGridFilterMenu'), { columns: [ { key: 'name', label: 'Name' }, { key: 'department', label: 'Department', filterType: 'select' }, { key: 'salary', label: 'Salary', filterType: 'number' }, { key: 'hired', label: 'Hired', filterType: 'date' } ], items, columnFilters: true, columnMenu: true, pagination: { pageSize: 8 }, sorting: false, virtualization: false }) ``` ## Custom column filters Two per-column hooks customize filtering. `filter` renders your own UI in a dedicated filter row (shown only for columns that define it) — the same factory contract as slots, and 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/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
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length], score: (i * 37) % 1000 })) new coreui.DataGrid(document.getElementById('dataGridCustomFilters'), { columnFilters: true, columns: [ { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 160, // Exact match - the default contains would also match partial values. filterFn: (value, filterValue) => value === filterValue, filter({ column }) { const select = document.createElement('select') select.className = 'form-select form-select-sm' for (const value of ['', ...[...column.getFacetedUniqueValues().keys()].toSorted()]) { const option = document.createElement('option') option.value = value option.textContent = value === '' ? 'All' : value select.append(option) } select.addEventListener('change', () => column.setFilterValue(select.value || undefined)) return { element: select } } }, { key: 'score', label: 'Score', width: 140, filterType: 'number' } ], 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` | `({ column, table, labels }) => { element, update?, dispose? }` | Custom filter UI rendered in the filter row instead of the filter button (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 emits `filterChange.coreui.data-grid` with `{ columnFilters, globalFilter }`. A custom [toolbar slot](https://coreui.io/data-grid/docs/features/slots/) that hosts its own search box still needs `globalFilter: true` for the query to reach the grid. --- # Data Grid Row Selection > Add a checkbox column to the 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/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

No rows selected

``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) const element = document.getElementById('dataGridRowSelection') new coreui.DataGrid(element, { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ], items, itemKey: item => String(item.id), // required for stable selection rowSelection: true, pagination: { pageSize: 10 } }) element.addEventListener('selectionChange.coreui.data-grid', event => { const count = event.selectedItems.length document.getElementById('dataGridRowSelectionMeta').textContent = count ? `${count} row${count === 1 ? '' : 's'} selected` : 'No rows selected' }) ``` ## Usage ```js new coreui.DataGrid(element, { columns, items, itemKey: (item) => String(item.id), // required for stable selection rowSelection: true, }) ``` 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/docs/features/pagination/) shows selection in action alongside a custom actions column. ## Reading the selection Call `grid.getSelectedItems()` for the selected objects, or listen for changes: ```js element.addEventListener('selectionChange.coreui.data-grid', (event) => { console.log(event.selectedItems) console.log(event.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/docs/columns/pinning/). In [server-side mode](https://coreui.io/data-grid/docs/features/server-side-data/), selection is id-keyed so it survives page changes, but `getSelectedItems()` returns only the items present in the current page's data. --- # Data Grid Keyboard Navigation > APG grid keyboard navigation for CoreUI 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
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridKeyboardNavigation'), { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ], items, itemKey: item => String(item.id), cellNavigation: true, rowSelection: true }) ``` ## Usage ```js new coreui.DataGrid(element, { columns, items, cellNavigation: true, }) ``` `cellNavigation` is off by default — without it the grid keeps native table semantics and the [accessible chrome](https://coreui.io/data-grid/docs/guides/accessibility/) it always had. [Inline editing](https://coreui.io/data-grid/docs/features/editing/) requires the active-cell model, so `editing: true` 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/docs/features/editing/) an editable cell. | | Escape | Ascend from cell content back to the cell. | | Space | Toggle [row selection](https://coreui.io/data-grid/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/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); } ``` --- # Data Grid Inline Editing > Inline cell editing for CoreUI 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/docs/features/keyboard-navigation/), so `editing: true` enables `cellNavigation` automatically. ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] let items = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + (i % 40), role: roles[i % roles.length] })) const element = document.getElementById('dataGridEditing') const grid = new coreui.DataGrid(element, { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name', editable: true, editValidate: value => (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 } } ], items, itemKey: item => String(item.id), editing: true }) // The grid never mutates items - apply the committed change yourself. // Replace the row and the array: the row model and cell values are memoized // by identity, so an in-place mutation would keep showing the old value. element.addEventListener('editCommit.coreui.data-grid', event => { items = items.map(row => (row === event.item ? { ...row, [event.columnId]: event.value } : row)) grid.setItems(items) }) ``` ## Usage Editing is opt-in per column — `editable` picks a built-in editor, a custom `editor` factory is itself the opt-in: ```js new coreui.DataGrid(element, { editing: true, columns: [ { key: 'name', editable: true }, // text input { key: 'age', editable: { type: 'number', min: 0 } }, // number input { key: 'role', editable: { type: 'select', options: ['admin', 'user'] } }, ], }) ``` ## The app owns the data The grid never mutates `items`. A commit fires `editCommit` with `{ item, columnId, value, previousValue }` — apply the change and hand the data back (client-side via [`setItems()`](https://coreui.io/data-grid/docs/api/methods/), server-side by PATCHing and refetching): ```js element.addEventListener('editCommit.coreui.data-grid', (event) => { // Replace the row and the array - the row model and cell values are // memoized by identity, so an in-place mutation keeps showing the old value. items = items.map((row) => (row === event.item ? { ...row, [event.columnId]: event.value } : row)) grid.setItems(items) // state-preserving swap }) ``` `editStart` and `editCancel` 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: ```js { 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 UI. It receives the editing context and returns the editor contract: ```js { key: 'note', editor: ({ item, column, value, commit, cancel, labels }) => { const input = document.createElement('input') input.className = 'form-control form-control-sm' input.value = String(value ?? '') return { element: input, focus: () => input.select(), getValue: () => input.value, } } } ``` | Key | Description | | --- | --- | | `element` | The editor's root element, rendered inside the cell (or the popup layer). | | `getValue?` | Returns the draft — feeds the Enter/Tab/blur/outside commits. Without it the grid can only cancel from the outside; call `commit(value)` yourself. | | `focus?` | Called once the editor is in the DOM; defaults to focusing the first form control. | | `contains?` | Extends the edit scope to elements outside `element` — overlays portaled to `body`. | | `dispose?` | Cleanup on commit/cancel. | | `popup?` | Render in an overlay anchored to the cell instead of inline (see below). | ## 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 — return `popup: true`. 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/coreui-pro`'s date range picker, time picker, autocomplete and multi-select 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 + editor + popup + anything `contains()` claims). Pin the component's `container` to the editor element, or claim a portaled overlay via `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; `editCommit` passes it through untouched. ```js { key: 'period', editor: ({ value, commit }) => { const element = document.createElement('div') const picker = new coreui.DateRangePicker(element, { startDate: value?.startDate, endDate: value?.endDate, container: element, // keep the calendar inside the edit scope }) return { element, popup: true, getValue: () => ({ startDate: picker.startDate, endDate: picker.endDate }), dispose: () => picker.dispose(), } } } ``` ## Interaction details - Scrolling the editing row out of the [virtualized](https://coreui.io/data-grid/docs/features/virtualization/) window commits the draft; so do sort, filter and page transitions. - `setItems()` and [server-side](https://coreui.io/data-grid/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. --- # Data Grid Undo & Redo > Undo and redo inline-edit commits in the CoreUI 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/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
``` ```js const roles = ['admin', 'editor', 'viewer'] let items = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, age: 20 + (i % 40), role: roles[i % roles.length] })) const element = document.getElementById('dataGridHistory') const grid = new coreui.DataGrid(element, { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name', editable: 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 } } ], items, itemKey: item => String(item.id), editing: true, history: true, toolbar: { history: true } }) // The grid never mutates items - undo/redo re-emit editCommit with the // values swapped, so this one handler covers edits AND their reversal. element.addEventListener('editCommit.coreui.data-grid', event => { items = items.map(row => (row === event.item ? { ...row, [event.columnId]: event.value } : row)) grid.setItems(items) }) ``` ## Usage ```js new coreui.DataGrid(element, { columns, items, editing: true, history: true, // track edit commits toolbar: { history: true }, // undo/redo buttons }) ``` The buttons stay disabled while their stack is empty; each undo/redo announces through the ARIA live region (`undoneAnnouncement`/`redoneAnnouncement` labels). `grid.undo()` / `grid.redo()` are also callable directly. ## How undo works — the app stays in charge The grid never mutates `items`. An undo **re-emits `editCommit`** with `value` and `previousValue` swapped (redo re-emits the original), so the same handler that applied the edit reverts it — no second code path: ```js element.addEventListener('editCommit.coreui.data-grid', (event) => { items = items.map((row) => (row === event.item ? { ...row, [event.columnId]: event.value } : row)) grid.setItems(items) }) ``` 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/docs/features/state/) instead. --- # Data Grid Save & Restore State > Persist the CoreUI Data Grid view — sorting, filters, column order, sizing, visibility, pinning, selection and page — to localStorage with a single option, or snapshot it programmatically. `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 visit**. No buttons, nothing to wire up. Sort or resize below, reload the page — the grid comes back as you left it. ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 200 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridState'), { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ], items, itemKey: item => String(item.id), columnVisibility: true, columnSizing: true, globalFilter: true, stateKey: 'docs-state-demo', toolbar: true }) ``` ## Usage ```js new coreui.DataGrid(element, { columns, items, itemKey: (item) => 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 constructed with the same key restores it before first render. ## Programmatic snapshots The persistence layer is optional — the snapshot API works without `stateKey`: ```js const state = grid.getState() // serializable: JSON.stringify(state) is safe grid.restoreState(state) // applies any subset of the slices ``` `getState()` returns `{ sorting, columnFilters, globalFilter, columnOrder, columnPinning, columnSizing, columnVisibility, pagination, rowSelection }`. Hand it to your own storage (a user-profile API, the URL) and feed it back through `restoreState()` — partial snapshots are fine, absent slices keep their current values. ## What is (and isn't) state - Row selection restores by row id — set [`itemKey`](https://coreui.io/data-grid/docs/api/options/) or the restored ids point at positions, not rows. - In [server-side mode](https://coreui.io/data-grid/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/docs/features/editing/) draft are not state. Data edits aren't either — undoing those is what [undo & redo](https://coreui.io/data-grid/docs/features/history/) is for. - Restoring fires the matching `*Change` events for every slice that changed — your app sees a restore exactly like user interaction. --- # Data Grid Pagination > Page through the 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/docs/features/server-side-data/) (which always paginates). Pagination is **mutually exclusive with [virtualization](https://coreui.io/data-grid/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
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridPagination'), { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110, formatter: value => String(value).toUpperCase() }, { key: 'actions', label: '', sortable: false, filterable: false, width: 120, render(item) { const button = document.createElement('button') button.type = 'button' button.className = 'btn btn-sm btn-outline-primary' button.textContent = 'Edit' button.addEventListener('click', () => alert(`Edit ${item.name} (#${item.id})`)) return button } } ], items, itemKey: item => String(item.id), pagination: { pageSize: 10 }, rowSelection: true }) ``` ## Options Pass `pagination: true` 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 emits `paginationChange.coreui.data-grid` with the grid's `{ pagination }` state. Drive paging yourself through the [headless table](https://coreui.io/data-grid/docs/api/headless/) — e.g. `grid.table.setPageIndex(3)`. --- # Data Grid Server-Side Data > Delegate sorting, filtering and pagination to your API with a single dataProvider function — the 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
``` ```js new coreui.DataGrid(document.getElementById('dataGridServer'), { columns: [ { key: 'first_name', label: 'First name' }, { key: 'last_name', label: 'Last name' }, { key: 'email', label: 'Email' }, { key: 'country', label: 'Country' }, { key: 'ip_address', label: 'IP' } ], itemKey: item => String(item.id), columnFilters: true, async dataProvider({ sorting, columnFilters, pagination }) { 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 } } }) ``` ## The dataProvider contract ```js new coreui.DataGrid(element, { columns, dataProvider: async ({ sorting, columnFilters, globalFilter, pagination }) => { // fetch from your API and return the matching page return { items, totalRows } }, pagination: true, // 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 emits `dataError.coreui.data-grid` `{ error }`, shows the empty state (`labels.loadError`) and leaves the grid interactive. - **Success.** Each load emits `dataLoad.coreui.data-grid` `{ items, totalRows }`. ## Selection semantics `rowSelection` is keyed by [`itemKey`](https://coreui.io/data-grid/docs/api/options/), so a selection survives page changes by design. `getSelectedItems()` returns 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/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/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. --- # Data Grid Toolbar > Add a built-in Data Grid toolbar with a column chooser, CSV export button and global search — enabled with a single option or configured granularly. The `toolbar` option 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/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/docs/features/slots/). ```js new coreui.DataGrid(element, { columns, items, columnVisibility: true, // required for the column chooser toolbar: true, // every action whose feature is enabled }) ``` `toolbar: true` enables each action whose underlying feature is on: **columns** needs [`columnVisibility`](https://coreui.io/data-grid/docs/columns/ordering-visibility/), **export** is always available, **undo/redo** needs [`history`](https://coreui.io/data-grid/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: true`](https://coreui.io/data-grid/docs/features/filtering/), so keep using whichever reads better. ```js new coreui.DataGrid(element, { columns, items, columnVisibility: true, toolbar: { columns: true, // column chooser popup export: { filename: 'users.csv' }, // CsvDownloadOptions pass-through history: true, // undo/redo buttons (needs history: true) search: true, // global search input }, }) ``` ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridToolbar'), { columns: [ { key: 'id', label: '#', width: 90, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ], items, itemKey: item => String(item.id), columnVisibility: true, 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 emit `visibilityChange.coreui.data-grid` just like the [column menu](https://coreui.io/data-grid/docs/columns/menu/). ## Export The export button downloads the current view as CSV by calling [`downloadCsv({ scope: 'filtered' })`](https://coreui.io/data-grid/docs/features/csv-export/). Pass a [`CsvDownloadOptions`](https://coreui.io/data-grid/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` string options (SVG markup, sanitized like every other icon). ## 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/docs/features/slots/) with the headless `table` and public helpers (`downloadCsv`, `column.toggleVisibility`). See the [column ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/) page for a slot-based chooser. --- # Data Grid Slots & Custom Rendering > Replace the Data Grid's toolbar, pagination and empty-state chrome with your own markup through the slots factory API. 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/docs/columns/overview/) instead. ## Custom slots Replace the grid's chrome — `toolbar`, `pagination` and `empty` — with your own markup. A slot is a factory `({ table, labels }) => ({ element, update?, dispose? })`: `element` is mounted in place of the built-in module, `update()` runs on every grid render (read state from the headless `table`), and `dispose()` runs on teardown. This demo swaps the built-in pagination for a minimal Previous/Next control driven entirely through `table`. ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridSlots'), { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ], items, itemKey: item => String(item.id), pagination: { pageSize: 10 }, slots: { pagination({ table }) { const element = document.createElement('div') element.className = 'd-flex gap-2 align-items-center mt-2' const prev = document.createElement('button') prev.className = 'btn btn-sm btn-outline-secondary' prev.textContent = 'Previous' prev.addEventListener('click', () => table.previousPage()) const next = document.createElement('button') next.className = 'btn btn-sm btn-outline-secondary' next.textContent = 'Next' next.addEventListener('click', () => table.nextPage()) const info = document.createElement('span') info.className = 'text-body-secondary' element.append(prev, info, next) return { element, update() { const { pageIndex } = table.getState().pagination info.textContent = `Page ${pageIndex + 1} of ${table.getPageCount()} · ${table.getRowCount()} items` prev.disabled = !table.getCanPreviousPage() next.disabled = !table.getCanNextPage() } } } } }) ``` Return a fresh `element` on every factory call — with `pagination.position: 'both'` the factory runs once per position. A custom toolbar that hosts its own global search still needs `globalFilter: true` for the query to reach the grid. The `empty` slot also renders when a server load fails; use the `dataError` event to tell the two apart. ## Slot contract | Slot | Replaces | Signature | | --- | --- | --- | | `toolbar` | The toolbar above the grid | `({ table, labels }) => ({ element, update?, dispose? })` | | `pagination` | The pagination bar | same | | `empty` | The no-rows / load-error state | same | - `element` — a DOM node mounted in place of the built-in module. - `update()` — runs on every grid render; read current state from the headless [`table`](https://coreui.io/data-grid/docs/api/headless/). - `dispose()` — runs on teardown; clean up listeners here. --- # Data Grid CSV Export > Export 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. `grid.getCsv()` returns a spec-compliant CSV string and `grid.downloadCsv()` 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 `grid.getCsv(options)` returns an RFC-4180 string; `grid.downloadCsv(options)` saves it as a file. 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. The pure helper is also published at `@coreui/data-grid/csv` — no runtime dependencies — for use without the component. ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) const grid = new coreui.DataGrid(document.getElementById('dataGridCsv'), { columns: [ { key: 'id', label: '#', width: 90 }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110, formatter: value => String(value).toUpperCase() } ], items, itemKey: item => String(item.id), columnFilters: true, pagination: { pageSize: 10 }, rowSelection: true }) document.getElementById('dataGridCsvBtn').addEventListener('click', () => { grid.downloadCsv({ filename: 'users.csv', scope: 'filtered', bom: true }) }) ``` ## 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. | --- # Data Grid Columns Overview > Define CoreUI 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/docs/api/options/) array. 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/docs/columns/sizing/), [pinning](https://coreui.io/data-grid/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/) and the [column menu](https://coreui.io/data-grid/docs/columns/menu/). ## Defining columns ```js new coreui.DataGrid(element, { columns: [ { key: 'name', label: 'Name' }, { key: 'email', label: 'Email' }, { key: 'role', label: 'Role' }, ], items, }) ``` `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: ```js { key: 'createdAt', label: 'Created', formatter: (value) => new Date(value).toLocaleDateString(), } ``` `formatter` output is also what [CSV export](https://coreui.io/data-grid/docs/features/csv-export/) writes. ## Rich cell content Use `render` for full custom cell content — action buttons, badges, links. `render` returns a DOM node or HTML string and is **never** used for CSV export: ```js { key: 'actions', label: '', render: (item) => { const button = document.createElement('button') button.className = 'btn btn-sm btn-primary' button.textContent = 'Edit' button.addEventListener('click', () => edit(item)) return button }, } ``` 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/docs/api/columns/) for every key. --- # Data Grid Column Sizing > Add drag-to-resize handles to 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: true` to add a drag handle to the right edge of every header cell. Widths follow the pointer live (`{ 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
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridSizing'), { columns: [ { key: 'id', label: '#', width: 90, resizable: false }, { key: 'name', label: 'Name', width: 220 }, { key: 'email', label: 'Email', width: 280 }, { key: 'role', label: 'Role', width: 160 } ], items, itemKey: item => String(item.id), columnSizing: true, // or { mode: 'onEnd' } to commit widths on release 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 emits `sizingChange.coreui.data-grid` with the grid's `{ columnSizing }` state. --- # Data Grid Column Pinning > Freeze 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: true` to enable the feature and pin later through the headless table (`grid.table.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
``` ```js const firstNames = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve'] const lastNames = ['Smith', 'Jones', 'Brown', 'Taylor', 'Wilson'] const countries = ['Poland', 'Germany', 'France', 'Spain', 'Italy'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, firstName: firstNames[i % firstNames.length], lastName: lastNames[i % lastNames.length], email: `user${i + 1}@example.com`, country: countries[i % countries.length] })) new coreui.DataGrid(document.getElementById('dataGridPinning'), { columns: [ { key: 'id', label: '#', width: 80 }, { key: 'firstName', label: 'First name', width: 160 }, { key: 'lastName', label: 'Last name', width: 160 }, { key: 'email', label: 'Email', width: 260 }, { key: 'country', label: 'Country', width: 200 }, { key: 'actions', label: '', sortable: false, width: 120, render(item) { const button = document.createElement('button') button.type = 'button' button.className = 'btn btn-sm btn-outline-primary' button.textContent = 'Edit' button.addEventListener('click', () => alert(`Edit ${item.firstName} (#${item.id})`)) return button } } ], items, itemKey: item => String(item.id), columnPinning: { left: ['id'], right: ['actions'] }, rowSelection: true, 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 emits `pinningChange.coreui.data-grid` with the grid's `{ columnPinning }` state. Reordering via [column ordering](https://coreui.io/data-grid/docs/columns/ordering-visibility/) never crosses a pinning boundary. --- # Data Grid Column Ordering & Visibility > Let users reorder 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: true` makes headers draggable — drop one onto another to reorder (dragging never crosses a pinning boundary); pass an array for an initial order. `columnVisibility: true` 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`. For a ready-made chooser, enable the built-in [toolbar](https://coreui.io/data-grid/docs/features/toolbar/) (`toolbar: { columns: true }`); this demo instead builds one entirely with the `toolbar` slot to show the headless API. ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridOrderVisibility'), { columns: [ { key: 'id', label: '#', width: 90, movable: false, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 110 } ], items, itemKey: item => String(item.id), columnOrder: true, columnVisibility: true, pagination: { pageSize: 10 }, slots: { toolbar({ table }) { const element = document.createElement('div') element.className = 'd-flex gap-3 mb-2' const inputs = [] for (const column of table.getAllLeafColumns()) { if (!column.getCanHide()) { continue } const label = document.createElement('label') label.className = 'form-check form-check-inline m-0' const input = document.createElement('input') input.type = 'checkbox' input.className = 'form-check-input me-1' input.addEventListener('change', () => column.toggleVisibility(input.checked)) label.append(input, column.id) element.append(label) inputs.push([column, input]) } return { element, update() { for (const [column, input] of inputs) { input.checked = column.getIsVisible() } } } } } }) ``` ## 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 emits `orderChange.coreui.data-grid` `{ columnOrder }`; toggling visibility emits `visibilityChange.coreui.data-grid` `{ columnVisibility }`. The [column menu](https://coreui.io/data-grid/docs/columns/menu/) offers a keyboard-accessible Move left/right as an alternative to drag-and-drop. --- # Data Grid Column Menu > Add a per-column header menu to the 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: true` 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
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) new coreui.DataGrid(document.getElementById('dataGridColumnMenu'), { columns: [ { key: 'id', label: '#', width: 90, movable: false, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 140 } ], items, itemKey: item => String(item.id), columnMenu: true, columnOrder: true, columnPinning: true, columnVisibility: true, 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: ```js columnMenu: ({ column, defaultActions }) => DataGridMenuAction[] ``` - **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/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: ```js { key: string, // unique id, also used to filter built-ins label: string, // menu item text icon?: string, // SVG markup, sanitized before insertion 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 sanitized against the SVG allow list, like every other Data Grid icon — see [icons](https://coreui.io/data-grid/docs/customization/styling/#icons). Set `sanitize: false` to opt out, or pass a `sanitizeFn` to plug in your own sanitizer. ```html
``` ```js const roles = ['admin', 'editor', 'viewer'] const items = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, role: roles[i % roles.length] })) const copyIcon = '' new coreui.DataGrid(document.getElementById('dataGridColumnMenuCustom'), { columns: [ { key: 'id', label: '#', width: 90, movable: false, hideable: false }, { key: 'name', label: 'Name' }, { key: 'email', label: 'Email', style: { width: '30%' } }, { key: 'role', label: 'Role', width: 140 } ], items, itemKey: item => 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 }) => [ ...defaultActions, { key: 'copy-header', label: 'Copy header', group: 'custom', icon: copyIcon, run: () => navigator.clipboard?.writeText(column.label ?? column.key) } ], columnOrder: true, columnPinning: true, columnVisibility: true, pagination: { pageSize: 10 } }) ``` ## Options | Option | Type | Default | Description | | --- | --- | --- | --- | | `columnMenu` | `boolean` \| `(context) => DataGridMenuAction[]` | `false` | Adds a per-column header menu. `true` builds it from the enabled features; a builder function returns the final action list (see [Customize the menu](#customize-the-menu)). | | `sanitize` | `boolean` | `true` | Sanitize icon markup (menu items and header icons) against the SVG allow list. | | `sanitizeFn` | `function` \| `null` | `null` | Custom sanitizer used in place of the built-in one. | The default menu's items depend on which features are on ([sorting](https://coreui.io/data-grid/docs/features/sorting/), [pinning](https://coreui.io/data-grid/docs/columns/pinning/), [ordering & visibility](https://coreui.io/data-grid/docs/columns/ordering-visibility/)). Its labels come from [`labels`](https://coreui.io/data-grid/docs/customization/localization/); see [Accessibility](https://coreui.io/data-grid/docs/guides/accessibility/) for the keyboard model. --- # Data Grid Styling & Theming > Theme the CoreUI 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: ```html
``` 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 #grid { --cui-data-grid-spacing: 0.75rem; --cui-data-grid-header-bg: var(--cui-tertiary-bg); --cui-data-grid-viewport-max-height: 40rem; } ``` ## 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. --- # Data Grid Localization > Translate every CoreUI Data Grid UI string through the labels option, with {token} interpolation for dynamic values. Every UI string the grid renders — menu items, pagination labels, ARIA announcements — comes from the `labels` option. Pass your own strings and they're merged over the defaults, so you only override what you need. ## Usage ```js new coreui.DataGrid(element, { columns, items, labels: { globalFilterPlaceholder: 'Szukaj…', pageSizeLabel: 'Wierszy na stronę', itemsInfo: '{first}–{last} z {total}', }, }) ``` ## 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/docs/features/filtering/) | | `applyFilter` | `Apply` | [Filter menu](https://coreui.io/data-grid/docs/features/filtering/) | | `clearFilter` | `Clear filter` | [Filter menu](https://coreui.io/data-grid/docs/features/filtering/) + quick-input clear | | `clearSort` | `Unsort` | [Column menu](https://coreui.io/data-grid/docs/columns/menu/) sort actions | | `columnMenu` | `Column options for {column}` | [Column menu](https://coreui.io/data-grid/docs/columns/menu/) button | | `filterAction` | `Filter…` | [Column menu](https://coreui.io/data-grid/docs/columns/menu/) action | | `filterColumn` | `Filter {column}` | [Filter](https://coreui.io/data-grid/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/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/docs/features/filtering/) | | `joinOr` | `OR` | [Filter menu](https://coreui.io/data-grid/docs/features/filtering/) | | `lastPage` | `Last page` | Pagination | | `loadError` | `Failed to load data` | [Server-side](https://coreui.io/data-grid/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/docs/features/history/) ARIA live announcement | | `resetColumns` | `Reset` | [Toolbar](https://coreui.io/data-grid/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/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/docs/features/toolbar/) column chooser | | `sortAscending` | `Sort ascending` | [Column menu](https://coreui.io/data-grid/docs/columns/menu/) sort actions | | `sortDescending` | `Sort descending` | [Column menu](https://coreui.io/data-grid/docs/columns/menu/) sort actions | | `toolbarColumns` | `Columns` | [Toolbar](https://coreui.io/data-grid/docs/features/toolbar/) columns button | | `toolbarExport` | `Export` | [Toolbar](https://coreui.io/data-grid/docs/features/toolbar/) export button | | `toolbarRedo` | `Redo` | [Toolbar](https://coreui.io/data-grid/docs/features/toolbar/) redo button | | `toolbarUndo` | `Undo` | [Toolbar](https://coreui.io/data-grid/docs/features/toolbar/) undo button | | `undoneAnnouncement` | `Change undone` | [Undo & redo](https://coreui.io/data-grid/docs/features/history/) ARIA live announcement | | `unpin` | `Unpin` | Column menu | The defaults are exported as `DEFAULT_LABELS` from `@coreui/data-grid` if you want to extend rather than replace them. --- # Data Grid Accessibility > How CoreUI 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/docs/resources/roadmap/). ## Grid keyboard navigation With [`cellNavigation`](https://coreui.io/data-grid/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/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/docs/columns/ordering-visibility/). All menu labels come from [`labels`](https://coreui.io/data-grid/docs/customization/localization/), so the menu is fully translatable. ## Selection The [selection](https://coreui.io/data-grid/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/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/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. --- # Data Grid Performance > How CoreUI Data Grid stays fast at 100,000 rows, the options 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/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/docs/columns/overview/) runs on the scroll hot path and returns a string, so it stays cheap. Reserve `render` (which builds DOM) for the columns that truly need interactive content. - **Debounced, race-safe fetches.** In [server-side mode](https://coreui.io/data-grid/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 DOM per visible cell. | ## 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/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. --- # Data Grid Options > Full reference of CoreUI Data Grid constructor options — columns, data, features and behavior. Pass options to the constructor: `new DataGrid(element, options)`. Update them later with `grid.update(options)`. | Option | Type | Default | Description | | --- | --- | --- | --- | | `cellNavigation` | `boolean` | `false` | APG grid keyboard navigation — `role="grid"`, a roving-tabindex active cell and arrow-key movement. See [Keyboard navigation](https://coreui.io/data-grid/docs/features/keyboard-navigation/). | | `columns` | `DataGridColumn[]` | `[]` | Column definitions — see [Columns](https://coreui.io/data-grid/docs/api/columns/). | | `columnFilters` | `boolean` | `false` | Renders a filter row in the header with an input per filterable column. See [Filtering](https://coreui.io/data-grid/docs/features/filtering/). | | `columnMenu` | `boolean \| ((ctx) => DataGridMenuAction[])` | `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 [Column menu](https://coreui.io/data-grid/docs/columns/menu/). | | `columnOrder` | `boolean \| string[]` | `false` | Drag-and-drop column reordering; an array sets the initial order. Reordering never crosses a pinning boundary. See [Column ordering](https://coreui.io/data-grid/docs/columns/ordering-visibility/). | | `columnPinning` | `boolean \| { left?: string[], right?: string[] }` | `false` | Freezes columns (by `key`) to the left/right edge with sticky positioning. `true` enables the feature with no initial pins. See [Column pinning](https://coreui.io/data-grid/docs/columns/pinning/). | | `columnVisibility` | `boolean \| Record` | `false` | Enables hiding/showing columns via `column.toggleVisibility()`; an object sets the initial visibility. | | `columnSizing` | `boolean \| { mode?: 'onChange' \| 'onEnd' }` | `false` | Enables column resize handles. `mode` controls whether widths update while dragging (`'onChange'`, the default) or on release (`'onEnd'`). See [Column sizing](https://coreui.io/data-grid/docs/columns/sizing/). | | `dataProvider` | `(request) => Promise<{ items, totalRows }>` | `null` | Server-side mode: the grid requests data on every sorting/filter/page change. The request carries `{ sorting, columnFilters, globalFilter, pagination }`. Implies pagination. See [Server-side data](https://coreui.io/data-grid/docs/features/server-side-data/). | | `editing` | `boolean` | `false` | Inline cell editing on Enter/F2/double-click for columns opting in via `editable`/`editor`; implies `cellNavigation`. See [Inline editing](https://coreui.io/data-grid/docs/features/editing/). | | `empty` | `string \| () => Node \| string` | `'No items found'` | Content shown when no rows match. | | `globalFilter` | `boolean` | `false` | Renders a search input above the grid that filters across all columns. Shorthand for `toolbar: { search: true }`. | | `history` | `boolean` | `false` | Undo/redo stack over edit commits — toolbar buttons and Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y. See [Undo & redo](https://coreui.io/data-grid/docs/features/history/). | | `itemKey` | `(item, index) => string` | `null` | Stable row id — required for selection to survive sorting and filtering. | | `items` | `object[]` | `[]` | Row data. | | `labels` | `Partial` | `{}` | UI strings (i18n), merged over the defaults. Supports `{token}` interpolation. See [Localization](https://coreui.io/data-grid/docs/customization/localization/). | | `overscan` | `number` | `10` | Extra rows rendered above and below the visible window. | | `pagination` | `boolean \| { pageSize?, pageSizeOptions?, position?, info? }` | `false` | Pagination mode — mutually exclusive with virtualization. See [Pagination](https://coreui.io/data-grid/docs/features/pagination/). | | `rowHeight` | `number` | `44` | Estimated row height in px used by the virtualizer. | | `rowSelection` | `boolean \| { selectAll? }` | `false` | Checkbox selection with select-all and shift+click ranges. See [Row selection](https://coreui.io/data-grid/docs/features/row-selection/). | | `sanitize` | `boolean` | `true` | Sanitize icon markup against the SVG allow list before inserting it. | | `sanitizeFn` | `(html) => string \| null` | `null` | Custom sanitizer used in place of the built-in one. | | `slots` | `{ toolbar?, pagination?, empty? }` | `{}` | Replaces built-in chrome. Each slot is a factory `({ table, labels }) => ({ element, update?, dispose? })`. See [Slots](https://coreui.io/data-grid/docs/features/slots/). | | `stateKey` | `string \| null` | `null` | Autosaves the grid state to localStorage under this key (debounced) and restores it on init. See [Save & restore state](https://coreui.io/data-grid/docs/features/state/). | | `sorting` | `boolean \| { multiple?, resetable? }` | `true` | Column sorting; shift+click adds columns to the sort. See [Sorting](https://coreui.io/data-grid/docs/features/sorting/). | | `toolbar` | `boolean \| { columns?, export?, history?, search? }` | `false` | Built-in toolbar above the grid. `true` enables every action whose feature is on; a granular object picks each. `columns` needs `columnVisibility`; `export` accepts a `CsvDownloadOptions` object; `history` needs the `history` option; `search` is the same input as `globalFilter`. See [Toolbar](https://coreui.io/data-grid/docs/features/toolbar/). | | `virtualization` | `boolean` | `true` | Windowed rendering for large datasets. See [Virtualization](https://coreui.io/data-grid/docs/features/virtualization/). | Every menu and header icon is overridable with its own `string` option (SVG markup): `columnMenuIcon`, `sortAscendingIcon`, `sortDescendingIcon`, `sortNeutralIcon`, `pinLeftIcon`, `pinRightIcon`, `unpinIcon`, `moveLeftIcon`, `moveRightIcon`, `hideColumnIcon`, `toolbarColumnsIcon`, `toolbarExportIcon`, `toolbarUndoIcon` and `toolbarRedoIcon`. They default to the CoreUI icon set and are sanitized like any other icon. --- # Data Grid Column API > Full reference of CoreUI Data Grid column definition keys — labels, sorting, filtering, formatting and custom rendering. Each entry in the [`columns`](https://coreui.io/data-grid/docs/api/options/) array describes one column. `key` is the only required field. See [Columns overview](https://coreui.io/data-grid/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/docs/features/editing/) with a built-in `text`, `number` or `select` editor. | | `editor` | `(context) => DataGridEditor` | Custom editor factory — receives `{ item, column, value, commit, cancel, labels }`, returns `{ element, getValue?, focus?, contains?, dispose?, popup? }`. Its presence opts the column in. | | `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` | `({ column, table, labels }) => { element, update?, dispose? }` | Custom filter UI rendered in the filter row instead of the filter button (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/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) => Node \| string` | Full custom cell content (e.g. action buttons). | | `width` | `number` | Initial column width in pixels - seeds `columnSizing` and drives the layout. | | `style` | `object` | 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/docs/features/csv-export/); `render` is for rich cell content and is never exported. Use one or the other per column. --- # Data Grid Events > CoreUI Data Grid events — namespaced *.coreui.data-grid, each carrying structured state. All events are namespaced `*.coreui.data-grid` and carry structured state. Listen with `element.addEventListener('sortingChange.coreui.data-grid', handler)`. | Event | Payload | | --- | --- | | `sortingChange` | `{ sorting: SortingState }` | | `filterChange` | `{ columnFilters: ColumnFiltersState, globalFilter: string }` | | `selectionChange` | `{ rowSelection: RowSelectionState, selectedItems: object[] }` | | `paginationChange` | `{ pagination: PaginationState }` | | `sizingChange` | `{ columnSizing: ColumnSizingState }` | | `pinningChange` | `{ columnPinning: ColumnPinningState }` | | `orderChange` | `{ columnOrder: ColumnOrderState }` | | `visibilityChange` | `{ columnVisibility: VisibilityState }` | | `editStart` | `{ item: object, columnId: string }` | | `editCommit` | `{ item: object, columnId: string, value: unknown, previousValue: unknown }` — the grid never mutates `items`; apply the change yourself. [Undo/redo](https://coreui.io/data-grid/docs/features/history/) re-emits it with the values swapped | | `editCancel` | `{ item: object, columnId: string }` | | `dataLoad` | `{ items: object[], totalRows: number }` (server-side mode) | | `dataError` | `{ error: unknown }` (server-side mode) | --- # Data Grid Methods > CoreUI Data Grid instance methods — selection, CSV export, updating options, disposal and the headless table getter. Call these on the `DataGrid` instance returned by the constructor. | Method | Description | | --- | --- | | `getSelectedItems()` | Returns the selected item objects. | | `getCsv(options?)` | Returns the rows as an RFC-4180 CSV string. Options: `scope`, `delimiter`, `bom`, `sanitize`. See [CSV export](https://coreui.io/data-grid/docs/features/csv-export/). | | `downloadCsv(options?)` | Downloads the CSV as a file. Same options plus `filename`. | | `getState()` | Serializable snapshot of every user-adjustable state slice. See [Save & restore state](https://coreui.io/data-grid/docs/features/state/). | | `restoreState(state)` | Applies a (partial) snapshot back onto the grid. | | `undo()` | Reverts the most recent [edit commit](https://coreui.io/data-grid/docs/features/history/) by re-emitting `editCommit` with the values swapped. Requires `history: true`. | | `redo()` | Re-applies the most recently undone edit commit. | | `setItems(items)` | Replaces the data in place, preserving sorting, filters, selection and scroll. Pair with `itemKey` so selection follows items rather than row indexes. Ignored in [server-side mode](https://coreui.io/data-grid/docs/features/server-side-data/), where the `dataProvider` owns the data. | | `update(options)` | Merges options and performs a full rebuild — sorting, filters, selection and scroll are reset. Use `setItems()` to swap data while keeping user state. | | `dispose()` | Destroys the instance and removes the rendered UI. | | `table` (getter) | The underlying headless table instance — the [headless escape hatch](https://coreui.io/data-grid/docs/api/headless/) for building custom UI. | --- # Data Grid Headless Table > Drop down to the underlying headless table instance to build custom 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 `table` getter — the same instance the grid renders from. ```js const grid = new coreui.DataGrid(element, { columns, items, pagination: false, // turn off built-in chrome you want to replace }) // Drive state imperatively through the underlying table instance: grid.table.setPageIndex(3) grid.table.getFilteredRowModel() grid.table.setColumnPinning({ left: ['name'] }) grid.table.getState().sorting ``` ## When to reach for it - **Custom chrome.** Build your own toolbar, pager or column chooser and wire it to `grid.table.*`. The [slots](https://coreui.io/data-grid/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.** `grid.table.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. Grid [events](https://coreui.io/data-grid/docs/api/events/) fire for headless-driven changes too. --- # 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/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/docs/features/keyboard-navigation/)** — `role="grid"` cell-by-cell arrow-key movement, extending the [accessible chrome](https://coreui.io/data-grid/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).