From 9513ca038020a15b7c69042a2232bc6368fcf0c9 Mon Sep 17 00:00:00 2001 From: John Betancur <1385932+jbetancur@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:42:37 -0400 Subject: [PATCH 1/2] feat: enhance comparisons with Material theme details for react-data-table-component --- apps/docs/src/pages/docs/comparisons.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/docs/src/pages/docs/comparisons.md b/apps/docs/src/pages/docs/comparisons.md index 2da070b3..a6f6b4a7 100644 --- a/apps/docs/src/pages/docs/comparisons.md +++ b/apps/docs/src/pages/docs/comparisons.md @@ -56,6 +56,7 @@ Choose **MUI X Data Grid** when: Choose **react-data-table-component** when: +- You want the Material look without adopting Material UI: the built-in `material` theme matches MUI's table styling with zero `@mui/material`/Emotion dependency - You are not using Material UI and do not want to adopt it for one component - You want zero runtime dependencies - Expandable rows and theming should work out of the box, free @@ -70,7 +71,7 @@ Choose **react-data-table-component** when: | Styled table out of the box | You build the UI | ✓ | ✓ | ✓ | | Sorting, pagination & selection UI | State only | ✓ | ✓ | ✓ | | Expandable row panels | State only | Enterprise tier | Pro tier | ✓ | -| Dark mode & themes | Bring your own | ✓ built-in | Via MUI theme | 5 built-in | +| Dark mode & themes | Bring your own | ✓ built-in | Via MUI theme | 5 built-in, including a Material-matching theme | | Requires a UI framework | No | No | @mui/material + Emotion | No | | Row virtualization | Via TanStack Virtual | ✓ built-in | ✓ built-in | ✗ (paginate instead) | | Size (min+gzip) | ~15 KB | ~338 KB | ~117 KB + MUI | ~35 KB | @@ -82,7 +83,7 @@ Sizes measured via Bundlephobia, July 2026 (TanStack v8.21, AG Grid v36, MUI X v - **Full render control, smallest bundle:** TanStack Table - **Spreadsheet-grade features and virtualized big data:** AG Grid -- **Already on Material UI:** MUI X Data Grid -- **A styled, working table in about 10 lines, with nothing to configure and no tier to upgrade to:** react-data-table-component +- **Already on Material UI and need Pro/Premium features like master/detail:** MUI X Data Grid +- **A fully-featured, production-ready table, including sorting, pagination, selection, expandable rows, and a Material-matching theme, with nothing gated behind a paid tier:** react-data-table-component See [Getting Started](/docs/getting-started) to try it, or the [live demo](/) on the homepage. From fa1d329aaa4e0e56eb34003eb23dd08b21726e63 Mon Sep 17 00:00:00 2001 From: John Betancur <1385932+jbetancur@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:37:55 -0400 Subject: [PATCH 2/2] feat: add live updates demo and row grouping demo (#1331) - Introduced LiveUpdatesDemo component for a real-time price grid with flashing rows on price changes. - Added RowGroupingDemo component to showcase grouping of orders by region with expandable rows and aggregate totals. - Updated DocsLayout to include links to new demos and recipes for live updates and row grouping. - Enhanced comparisons documentation to include Material UI Table. - Created new recipes for CSV import, editable inventory grid, and grouped rows with aggregates. - Refactored existing recipe pages to utilize a consistent demo component for better layout and presentation. --- CLAUDE.md | 6 + apps/docs/public/llms.txt | 5 + .../src/components/demos/CsvImportDemo.tsx | 79 +++++++++++++ .../demos/DashboardDrilldownDemo.tsx | 44 +++++-- .../src/components/demos/LiveUpdatesDemo.tsx | 81 +++++++++++++ .../src/components/demos/RowGroupingDemo.tsx | 101 ++++++++++++++++ apps/docs/src/layouts/DocsLayout.astro | 9 +- apps/docs/src/pages/docs/cells.astro | 4 + apps/docs/src/pages/docs/comparisons.md | 49 +++++--- .../src/pages/docs/conditional-styles.astro | 4 + apps/docs/src/pages/docs/expandable.astro | 25 ++++ apps/docs/src/pages/docs/export.astro | 4 + apps/docs/src/pages/docs/filtering.astro | 5 + apps/docs/src/pages/docs/fixed-header.astro | 4 + apps/docs/src/pages/docs/inline-editing.astro | 4 + apps/docs/src/pages/docs/pagination.astro | 5 + apps/docs/src/pages/docs/recipes.astro | 20 ++++ .../docs/recipes/bulk-action-toolbar.astro | 14 ++- .../src/pages/docs/recipes/csv-import.astro | 69 +++++++++++ .../pages/docs/recipes/editable-grid.astro | 14 ++- .../pages/docs/recipes/inventory-editor.astro | 88 ++++++++++++++ .../src/pages/docs/recipes/live-updates.astro | 74 ++++++++++++ .../pages/docs/recipes/master-detail.astro | 57 +++++++-- .../docs/recipes/persist-column-widths.astro | 14 ++- .../src/pages/docs/recipes/row-grouping.astro | 14 ++- .../src/pages/docs/recipes/row-groups.astro | 111 ++++++++++++++++++ .../src/pages/docs/recipes/server-side.astro | 14 ++- .../pages/docs/recipes/sticky-footer.astro | 14 ++- apps/docs/src/pages/docs/sorting.astro | 5 + apps/docs/src/pages/index.astro | 2 +- 30 files changed, 890 insertions(+), 49 deletions(-) create mode 100644 apps/docs/src/components/demos/CsvImportDemo.tsx create mode 100644 apps/docs/src/components/demos/LiveUpdatesDemo.tsx create mode 100644 apps/docs/src/components/demos/RowGroupingDemo.tsx create mode 100644 apps/docs/src/pages/docs/recipes/csv-import.astro create mode 100644 apps/docs/src/pages/docs/recipes/inventory-editor.astro create mode 100644 apps/docs/src/pages/docs/recipes/live-updates.astro create mode 100644 apps/docs/src/pages/docs/recipes/row-groups.astro diff --git a/CLAUDE.md b/CLAUDE.md index 5fce620a..188cff13 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,12 @@ The docs live in `apps/docs/src/pages/docs/`. Each feature has its own `.astro` - Add new pages to the sidebar in `apps/docs/src/layouts/DocsLayout.astro`. - If removing a page, add a `[[redirects]]` entry in `netlify.toml` pointing the old URL to its replacement. +**Keeping `apps/docs/public/llms.txt` in sync:** + +- Every docs page listed in `DocsLayout.astro`'s sidebar nav should have a matching entry in `llms.txt`, with a one-line factual (non-marketing) description. +- Add/remove/re-describe its `llms.txt` entry in the same change whenever a docs page is added, removed, or its scope changes materially — this applies especially to `comparisons.md`, since claims there go stale fastest. +- Keep descriptions neutral and factual — `llms.txt` is read by LLMs/crawlers, not just humans; slanted copy reads as manipulation and can get the file distrusted or ignored. + --- ## Updating CHANGELOG.md diff --git a/apps/docs/public/llms.txt b/apps/docs/public/llms.txt index 287e5326..ff0e1436 100644 --- a/apps/docs/public/llms.txt +++ b/apps/docs/public/llms.txt @@ -22,6 +22,7 @@ const columns = [ - [Getting Started](https://reactdatatable.com/docs/getting-started): install and render a first table - [Installation](https://reactdatatable.com/docs/installation): package managers, CSS import, framework setup - [API Reference](https://reactdatatable.com/docs/api): all props and types +- [Comparisons](https://reactdatatable.com/docs/comparisons): how this compares to TanStack Table, AG Grid, MUI X Data Grid, and the plain MUI Table - [Migration Guide](https://reactdatatable.com/docs/migration): upgrading from v7 to v8 ## Columns @@ -73,6 +74,10 @@ const columns = [ - [Approval workflow with bulk actions](https://reactdatatable.com/docs/recipes/bulk-action-toolbar) - [URL-synced table state](https://reactdatatable.com/docs/recipes/editable-grid) - [Dashboard drill-down](https://reactdatatable.com/docs/recipes/master-detail) +- [Live-updating data grid](https://reactdatatable.com/docs/recipes/live-updates): flash-on-change rows using conditionalRowStyles and keyField +- [Editable inventory grid](https://reactdatatable.com/docs/recipes/inventory-editor): spreadsheet-style inline editing with a derived total column +- [Grouped rows with aggregates](https://reactdatatable.com/docs/recipes/row-groups): per-group totals and drill-down without a dedicated groupBy API +- [CSV import](https://reactdatatable.com/docs/recipes/csv-import): load a user-uploaded CSV file into the table - [Audit log viewer](https://reactdatatable.com/docs/recipes/sticky-footer) - [Persist column widths](https://reactdatatable.com/docs/recipes/persist-column-widths) - [Inline row actions](https://reactdatatable.com/docs/recipes/row-grouping) diff --git a/apps/docs/src/components/demos/CsvImportDemo.tsx b/apps/docs/src/components/demos/CsvImportDemo.tsx new file mode 100644 index 00000000..af0cd727 --- /dev/null +++ b/apps/docs/src/components/demos/CsvImportDemo.tsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import DataTable, { type TableColumn } from 'react-data-table-component'; + +interface Contact { + name: string; + email: string; + company: string; +} + +const SAMPLE_CSV = `name,email,company +Aria Chen,aria@acme.com,Acme Corp +Marcus Webb,marcus@blueharbor.com,Blue Harbor Inc +Priya Kapoor,priya@candlesys.com,Candle Systems`; + +// Naive CSV parser: no quoted-comma support. For production, use a proper CSV library. +function parseCsv(text: string): Contact[] { + const [headerLine, ...lines] = text.trim().split('\n'); + const headers = headerLine.split(',').map(h => h.trim()); + + return lines.filter(Boolean).map(line => { + const cells = line.split(',').map(c => c.trim()); + return Object.fromEntries(headers.map((h, i) => [h, cells[i] ?? ''])) as unknown as Contact; + }); +} + +const columns: TableColumn[] = [ + { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, + { id: 'email', name: 'Email', selector: r => r.email, sortable: true, grow: 1 }, + { id: 'company', name: 'Company', selector: r => r.company, sortable: true }, +]; + +export default function CsvImportDemo() { + const [data, setData] = useState([]); + const [error, setError] = useState(null); + + function handleFile(file: File) { + const reader = new FileReader(); + reader.onload = () => { + try { + setData(parseCsv(String(reader.result))); + setError(null); + } catch { + setError('Could not parse that file as CSV.'); + } + }; + reader.readAsText(file); + } + + return ( +
+
+ + + {error && {error}} +
+
+ Upload a CSV or load the sample to see it here
} + /> +
+ + ); +} diff --git a/apps/docs/src/components/demos/DashboardDrilldownDemo.tsx b/apps/docs/src/components/demos/DashboardDrilldownDemo.tsx index 18bbb160..ec763307 100644 --- a/apps/docs/src/components/demos/DashboardDrilldownDemo.tsx +++ b/apps/docs/src/components/demos/DashboardDrilldownDemo.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import DataTable, { type ConditionalStyles, type ExpanderComponentProps, type TableColumn } from 'react-data-table-component'; interface TeamMember { @@ -90,20 +90,31 @@ function SpendBar({ budget, spent }: { budget: number; spent: number }) { ); } -const memberColumns: TableColumn[] = [ - { name: 'Name', selector: m => m.name, grow: 1 }, - { name: 'Role', selector: m => m.role, grow: 1 }, - { name: 'Open tickets',selector: m => m.tickets, width: '110px', right: true }, - { name: 'Utilization', selector: m => m.utilization, width: '160px', - cell: m => , - }, -]; +function initials(name: string) { + return name.split(' ').map(part => part[0]).join('').slice(0, 2).toUpperCase(); +} function DepartmentDetail({ data: dept }: ExpanderComponentProps) { return (

Team members

- +
+ {dept.members.map(member => ( +
+
+ {initials(member.name)} +
+
+
+

{member.name}

+ {member.tickets} open +
+

{member.role}

+ +
+
+ ))} +
); } @@ -145,15 +156,26 @@ const columns: TableColumn[] = [ ]; export default function DashboardDrilldownDemo() { + const [allExpanded, setAllExpanded] = useState(false); + return (
-

Expand any row to see team member utilization.

+
+

Expand any row to see team member utilization.

+ +
allExpanded} conditionalRowStyles={conditionalRowStyles} defaultSortFieldId="name" highlightOnHover diff --git a/apps/docs/src/components/demos/LiveUpdatesDemo.tsx b/apps/docs/src/components/demos/LiveUpdatesDemo.tsx new file mode 100644 index 00000000..204c0b63 --- /dev/null +++ b/apps/docs/src/components/demos/LiveUpdatesDemo.tsx @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from 'react'; +import DataTable, { type ConditionalStyles, type TableColumn } from 'react-data-table-component'; + +interface Ticker { + symbol: string; + name: string; + price: number; + change: number; +} + +const INITIAL: Ticker[] = [ + { symbol: 'ACME', name: 'Acme Corp', price: 182.4, change: 0 }, + { symbol: 'BLTC', name: 'Baltic Freight', price: 64.12, change: 0 }, + { symbol: 'CNDL', name: 'Candle Systems', price: 305.9, change: 0 }, + { symbol: 'DYNM', name: 'Dynamo Energy', price: 48.77, change: 0 }, + { symbol: 'EVRG', name: 'Evergreen Foods', price: 96.3, change: 0 }, +]; + +const columns: TableColumn[] = [ + { id: 'symbol', name: 'Symbol', selector: r => r.symbol, sortable: true, width: '110px', style: { fontWeight: 600 } }, + { id: 'name', name: 'Name', selector: r => r.name, grow: 1 }, + { + id: 'price', + name: 'Price', + selector: r => r.price, + right: true, + width: '110px', + format: r => `$${r.price.toFixed(2)}`, + }, + { + id: 'change', + name: 'Change', + selector: r => r.change, + right: true, + width: '110px', + cell: r => ( + 0 ? 'text-green-600' : 'text-red-600'}> + {r.change === 0 ? '—' : `${r.change > 0 ? '▲' : '▼'} ${Math.abs(r.change).toFixed(2)}`} + + ), + }, +]; + +export default function LiveUpdatesDemo() { + const [data, setData] = useState(INITIAL); + const [flashed, setFlashed] = useState>(new Set()); + + useEffect(() => { + const tick = setInterval(() => { + const symbol = INITIAL[Math.floor(Math.random() * INITIAL.length)].symbol; + const delta = +(Math.random() * 4 - 2).toFixed(2); + + setData(prev => prev.map(r => (r.symbol === symbol ? { ...r, price: +(r.price + delta).toFixed(2), change: delta } : r))); + setFlashed(prev => new Set(prev).add(symbol)); + setTimeout(() => setFlashed(prev => { + const next = new Set(prev); + next.delete(symbol); + return next; + }), 600); + }, 1200); + + return () => clearInterval(tick); + }, []); + + const conditionalRowStyles: ConditionalStyles[] = [ + { when: r => flashed.has(r.symbol), style: { backgroundColor: '#fefce8', transition: 'background-color 0.6s ease' } }, + ]; + + return ( +
+ +
+ ); +} diff --git a/apps/docs/src/components/demos/RowGroupingDemo.tsx b/apps/docs/src/components/demos/RowGroupingDemo.tsx new file mode 100644 index 00000000..046f3a9c --- /dev/null +++ b/apps/docs/src/components/demos/RowGroupingDemo.tsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; +import DataTable, { type ExpanderComponentProps, type TableColumn } from 'react-data-table-component'; + +interface Order { + id: number; + region: string; + customer: string; + amount: number; +} + +interface RegionGroup { + region: string; + count: number; + total: number; + orders: Order[]; +} + +const ORDERS: Order[] = [ + { id: 1, region: 'West', customer: 'Acme Corp', amount: 4200 }, + { id: 2, region: 'West', customer: 'Blue Harbor Inc', amount: 1850 }, + { id: 3, region: 'West', customer: 'Candle Systems', amount: 3100 }, + { id: 4, region: 'East', customer: 'Dynamo Energy', amount: 2600 }, + { id: 5, region: 'East', customer: 'Evergreen Foods', amount: 5400 }, + { id: 6, region: 'Central', customer: 'Frontier Retail', amount: 990 }, + { id: 7, region: 'Central', customer: 'Granite Works', amount: 2200 }, + { id: 8, region: 'Central', customer: 'Harbor Logistics', amount: 1475 }, + { id: 9, region: 'Central', customer: 'Ironclad Freight', amount: 3050 }, +]; + +// Group flat rows into per-region summary rows — a plain reduce, no groupBy API required. +const groups: RegionGroup[] = Object.values( + ORDERS.reduce>((acc, order) => { + const g = (acc[order.region] ??= { region: order.region, count: 0, total: 0, orders: [] }); + g.count += 1; + g.total += order.amount; + g.orders.push(order); + return acc; + }, {}), +); + +const columns: TableColumn[] = [ + { id: 'region', name: 'Region', selector: r => r.region, sortable: true, style: { fontWeight: 600 } }, + { id: 'count', name: 'Orders', selector: r => r.count, sortable: true, right: true, width: '110px' }, + { + id: 'total', + name: 'Total', + selector: r => r.total, + sortable: true, + right: true, + width: '130px', + format: r => `$${r.total.toLocaleString()}`, + footer: (rows: RegionGroup[]) => `$${rows.reduce((sum, r) => sum + r.total, 0).toLocaleString()}`, + }, +]; + +function RegionOrders({ data }: ExpanderComponentProps) { + return ( +
+
+ {data.orders.map(order => ( +
+ {order.customer} + ${order.amount.toLocaleString()} +
+ ))} +
+
+ ); +} + +export default function RowGroupingDemo() { + const [allExpanded, setAllExpanded] = useState(false); + + return ( +
+
+ +
+
+ allExpanded} + highlightOnHover + dense + /> +
+
+ ); +} diff --git a/apps/docs/src/layouts/DocsLayout.astro b/apps/docs/src/layouts/DocsLayout.astro index 9320c4d1..2d2ec67e 100644 --- a/apps/docs/src/layouts/DocsLayout.astro +++ b/apps/docs/src/layouts/DocsLayout.astro @@ -18,6 +18,7 @@ const nav = [ { label: 'Installation', href: '/docs/installation' }, { label: 'Comparisons', href: '/docs/comparisons' }, { label: 'Changelog', href: '/docs/changelog' }, + { label: 'llms.txt', href: '/llms.txt' }, ], }, { @@ -97,6 +98,10 @@ const nav = [ { label: 'Audit log viewer', href: '/docs/recipes/sticky-footer' }, { label: 'Persist column widths', href: '/docs/recipes/persist-column-widths' }, { label: 'Inline row actions', href: '/docs/recipes/row-grouping' }, + { label: 'Live-updating grid', href: '/docs/recipes/live-updates' }, + { label: 'Editable inventory grid', href: '/docs/recipes/inventory-editor' }, + { label: 'Grouped rows with aggregates', href: '/docs/recipes/row-groups' }, + { label: 'CSV import', href: '/docs/recipes/csv-import' }, ], }, { @@ -110,8 +115,8 @@ const nav = [ const currentPath = Astro.url.pathname; -// Flatten nav to a single ordered list for prev/next -const allLinks = nav.flatMap(({ links }) => links); +// Flatten nav to a single ordered list for prev/next (excludes non-page links like llms.txt) +const allLinks = nav.flatMap(({ links }) => links).filter(({ href }) => href.startsWith('/docs/')); const currentIndex = allLinks.findIndex(({ href }) => currentPath === href); const prevLink = currentIndex > 0 ? allLinks[currentIndex - 1] : null; const nextLink = currentIndex < allLinks.length - 1 ? allLinks[currentIndex + 1] : null; diff --git a/apps/docs/src/pages/docs/cells.astro b/apps/docs/src/pages/docs/cells.astro index dc32bceb..70f832eb 100644 --- a/apps/docs/src/pages/docs/cells.astro +++ b/apps/docs/src/pages/docs/cells.astro @@ -155,4 +155,8 @@ const columns: TableColumn[] = [ > + +

+ See it combined with other features in the Inline row actions recipe. +

diff --git a/apps/docs/src/pages/docs/comparisons.md b/apps/docs/src/pages/docs/comparisons.md index a6f6b4a7..85d3db7e 100644 --- a/apps/docs/src/pages/docs/comparisons.md +++ b/apps/docs/src/pages/docs/comparisons.md @@ -1,12 +1,12 @@ --- layout: '../../layouts/DocsLayout.astro' -title: 'Comparisons: TanStack Table, AG Grid, MUI X | react-data-table-component' -description: 'How react-data-table-component compares to TanStack Table, AG Grid Community, and MUI X Data Grid: setup effort, bundle size, features, and pricing.' +title: 'Comparisons: TanStack Table, AG Grid, MUI X, MUI Table | react-data-table-component' +description: 'How react-data-table-component compares to TanStack Table, AG Grid Community, MUI X Data Grid, and the plain @mui/material Table: setup effort, bundle size, features, and pricing.' --- # How react-data-table-component compares -All four libraries on this page are solid, actively maintained choices. They differ in how much you assemble yourself, what ships free, and what ecosystem they assume. This page gives you the honest picture so you can pick the right tool, even if it is not this one. +All five options on this page are solid, actively maintained choices. They differ in how much you assemble yourself, what ships free, and what ecosystem they assume. This page gives you the honest picture so you can pick the right tool, even if it is not this one. The short version: react-data-table-component is a **full component**. You pass `columns` and `data` and get a styled, sortable, paginated table with selection, expandable rows, and theming. Nothing is gated behind a paid tier. @@ -57,33 +57,50 @@ Choose **MUI X Data Grid** when: Choose **react-data-table-component** when: - You want the Material look without adopting Material UI: the built-in `material` theme matches MUI's table styling with zero `@mui/material`/Emotion dependency -- You are not using Material UI and do not want to adopt it for one component +- You are already on Material UI but don't want to add `@mui/x-data-grid` (and its Pro/Premium tier) for one component: the `material` theme gives you a matching table without the extra dependency - You want zero runtime dependencies - Expandable rows and theming should work out of the box, free +## react-data-table-component vs Material UI Table + +`@mui/material`'s plain `Table` (paired with `TableHead`, `TableBody`, `TableRow`, `TableCell`, `TablePagination`) is free and ships with core Material UI, no `@mui/x-data-grid` purchase or package required. It is the **atomic** option: the primitives are styled and theme-aware, but sorting, pagination, and selection are not implemented for you, you wire up the state and event handlers yourself, similar to TanStack Table but with Material markup instead of plain HTML. + +Choose **Material UI Table** when: + +- You want Material-styled table primitives that you compose and control yourself +- Your app is already on Material UI and the table is simple enough that hand-rolling sorting/pagination isn't much work +- You don't need built-in sorting, pagination, or selection logic + +Choose **react-data-table-component** when: + +- You want sorting, pagination, selection, and expandable rows working out of the box instead of hand-rolled +- You want the Material look (via the built-in `material` theme) without requiring `@mui/material` + Emotion as dependencies +- You want one component that can switch between multiple themes, not just Material + --- ## Feature comparison -| | TanStack Table | AG Grid Community | MUI X Data Grid | react-data-table-component | -| --- | --- | --- | --- | --- | -| Approach | Headless | Full data grid | Full data grid | Full component | -| Styled table out of the box | You build the UI | ✓ | ✓ | ✓ | -| Sorting, pagination & selection UI | State only | ✓ | ✓ | ✓ | -| Expandable row panels | State only | Enterprise tier | Pro tier | ✓ | -| Dark mode & themes | Bring your own | ✓ built-in | Via MUI theme | 5 built-in, including a Material-matching theme | -| Requires a UI framework | No | No | @mui/material + Emotion | No | -| Row virtualization | Via TanStack Virtual | ✓ built-in | ✓ built-in | ✗ (paginate instead) | -| Size (min+gzip) | ~15 KB | ~338 KB | ~117 KB + MUI | ~35 KB | -| Paid tiers | None | Enterprise | Pro & Premium | None | +| | TanStack Table | AG Grid Community | MUI X Data Grid | Material UI Table | react-data-table-component | +| --- | --- | --- | --- | --- | --- | +| Approach | Headless | Full data grid | Full data grid | Atomic primitives | Full component | +| Styled table out of the box | You build the UI | ✓ | ✓ | ✓ (primitives only) | ✓ | +| Sorting, pagination & selection UI | State only | ✓ | ✓ | You build both | ✓ | +| Expandable row panels | State only | Enterprise tier | Pro tier | You build it | ✓ | +| Dark mode & themes | Bring your own | ✓ built-in | Via MUI theme | Via MUI theme | 5 built-in, including a Material-matching theme | +| Requires a UI framework | No | No | @mui/material + Emotion | @mui/material + Emotion | No | +| Row virtualization | Via TanStack Virtual | ✓ built-in | ✓ built-in | Manual | ✗ (paginate instead) | +| Size (min+gzip) | ~15 KB | ~338 KB | ~117 KB + MUI | ~147 KB (MUI core) | ~35 KB | +| Paid tiers | None | Enterprise | Pro & Premium | None | None | -Sizes measured via Bundlephobia, July 2026 (TanStack v8.21, AG Grid v36, MUI X v9.8). +Sizes measured via Bundlephobia, July 2026 (TanStack v8.21, AG Grid v36, MUI X v9.8, @mui/material v9.2). ## Which should you choose? - **Full render control, smallest bundle:** TanStack Table - **Spreadsheet-grade features and virtualized big data:** AG Grid - **Already on Material UI and need Pro/Premium features like master/detail:** MUI X Data Grid +- **Already on Material UI, table is simple, and you don't mind wiring up sorting/pagination yourself:** Material UI Table - **A fully-featured, production-ready table, including sorting, pagination, selection, expandable rows, and a Material-matching theme, with nothing gated behind a paid tier:** react-data-table-component See [Getting Started](/docs/getting-started) to try it, or the [live demo](/) on the homepage. diff --git a/apps/docs/src/pages/docs/conditional-styles.astro b/apps/docs/src/pages/docs/conditional-styles.astro index 23132179..7148ba77 100644 --- a/apps/docs/src/pages/docs/conditional-styles.astro +++ b/apps/docs/src/pages/docs/conditional-styles.astro @@ -105,6 +105,10 @@ const conditionalRowStyles: ConditionalStyles[] = [ expandableInheritConditionalStyles.

+

+ See it combined with other features in the Live-updating data grid recipe. +

+ row.locked === true} />`} /> +

Expand all / collapse all

+

+ expandableRowExpanded re-syncs any time its result changes, so a single boolean + in state — driven by an "Expand all" button — expands or collapses every row at once. Rows a + user has manually toggled since then are left alone until the shared state changes again: +

+ setAllExpanded(v => !v)}> + {allExpanded ? 'Collapse all' : 'Expand all'} + + + allExpanded} +/>`} /> +

Expand on row click

Instead of requiring the user to click the expander icon, you can expand/collapse on a @@ -253,6 +273,11 @@ export default function App() { ]} /> +

+ See it combined with other features in the Dashboard with drill-down recipe + and Grouped rows with aggregates. +

+

Localization

Override the aria-labels on the expand/collapse button via the localization prop. diff --git a/apps/docs/src/pages/docs/export.astro b/apps/docs/src/pages/docs/export.astro index a5b741ec..ebabade4 100644 --- a/apps/docs/src/pages/docs/export.astro +++ b/apps/docs/src/pages/docs/export.astro @@ -242,4 +242,8 @@ function App() { ['copy', '(format?) => Promise<void>', 'Copy to clipboard. Rejects when the clipboard API is unavailable.'], ]} /> + +

+ Need the reverse direction? See the CSV import recipe. +

diff --git a/apps/docs/src/pages/docs/filtering.astro b/apps/docs/src/pages/docs/filtering.astro index 0bf8444a..b8feaf10 100644 --- a/apps/docs/src/pages/docs/filtering.astro +++ b/apps/docs/src/pages/docs/filtering.astro @@ -241,4 +241,9 @@ function onApply(columnId: string | number, filter: FilterState) { // Apply all active filters before rendering rows const rows = filteredData(tableRows);`} /> + +

+ See it combined with other features in the Server-side sort, page & filter recipe + and URL-synced table state. +

diff --git a/apps/docs/src/pages/docs/fixed-header.astro b/apps/docs/src/pages/docs/fixed-header.astro index eae726a4..209e7576 100644 --- a/apps/docs/src/pages/docs/fixed-header.astro +++ b/apps/docs/src/pages/docs/fixed-header.astro @@ -117,4 +117,8 @@ const columns: TableColumn[] = [ ['onScroll', '(event: React.UIEvent<HTMLDivElement>) => void', '-', 'Called when the user scrolls the table body. Works with both fixedHeader enabled and disabled.'], ]} /> + +

+ See it combined with other features in the Audit log viewer recipe. +

diff --git a/apps/docs/src/pages/docs/inline-editing.astro b/apps/docs/src/pages/docs/inline-editing.astro index 78855f7a..ba5e132f 100644 --- a/apps/docs/src/pages/docs/inline-editing.astro +++ b/apps/docs/src/pages/docs/inline-editing.astro @@ -444,4 +444,8 @@ interface CustomCellEditorContext { cancel: () => void; column: TableColumn; }`} /> + +

+ See it combined with other features in the Editable inventory grid recipe. +

diff --git a/apps/docs/src/pages/docs/pagination.astro b/apps/docs/src/pages/docs/pagination.astro index f4ddbfb2..2f6f22e0 100644 --- a/apps/docs/src/pages/docs/pagination.astro +++ b/apps/docs/src/pages/docs/pagination.astro @@ -547,4 +547,9 @@ const custom: Localization = { }; `} /> + +

+ See it combined with other features in the Server-side sort, page & filter recipe + and URL-synced table state. +

diff --git a/apps/docs/src/pages/docs/recipes.astro b/apps/docs/src/pages/docs/recipes.astro index 71f004bb..9d759502 100644 --- a/apps/docs/src/pages/docs/recipes.astro +++ b/apps/docs/src/pages/docs/recipes.astro @@ -37,6 +37,26 @@ const recipes = [ title: 'Inline row actions', desc: 'Per-row ⋮ menu with edit, duplicate, and delete — confirmation modals included.', }, + { + href: '/docs/recipes/live-updates', + title: 'Live-updating data grid', + desc: 'Flash-on-change rows for a real-time price grid, using conditionalRowStyles and keyField.', + }, + { + href: '/docs/recipes/inventory-editor', + title: 'Editable inventory grid', + desc: 'Spreadsheet-style inline editing with a derived total column and a running grand total.', + }, + { + href: '/docs/recipes/row-groups', + title: 'Grouped rows with aggregates', + desc: 'Per-group totals and drill-down built from a plain array reduce — no groupBy API needed.', + }, + { + href: '/docs/recipes/csv-import', + title: 'CSV import', + desc: 'Load a user-uploaded CSV file into the table with FileReader and a small parser.', + }, ]; --- diff --git a/apps/docs/src/pages/docs/recipes/bulk-action-toolbar.astro b/apps/docs/src/pages/docs/recipes/bulk-action-toolbar.astro index 63cbb645..8cc19ae8 100644 --- a/apps/docs/src/pages/docs/recipes/bulk-action-toolbar.astro +++ b/apps/docs/src/pages/docs/recipes/bulk-action-toolbar.astro @@ -1,7 +1,9 @@ --- import DocsLayout from '../../../layouts/DocsLayout.astro'; import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; import ApprovalWorkflowDemo from '../../../components/demos/ApprovalWorkflowDemo.tsx'; +import approvalWorkflowSource from '../../../components/demos/ApprovalWorkflowDemo.tsx?raw'; --- @@ -13,9 +15,17 @@ import ApprovalWorkflowDemo from '../../../components/demos/ApprovalWorkflowDemo automatically excluded from the action, so reviewers can't accidentally re-process them.

-
+ -
+
+ +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

Disabling already-resolved rows

diff --git a/apps/docs/src/pages/docs/recipes/csv-import.astro b/apps/docs/src/pages/docs/recipes/csv-import.astro new file mode 100644 index 00000000..5e6b1f84 --- /dev/null +++ b/apps/docs/src/pages/docs/recipes/csv-import.astro @@ -0,0 +1,69 @@ +--- +import DocsLayout from '../../../layouts/DocsLayout.astro'; +import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; +import CsvImportDemo from '../../../components/demos/CsvImportDemo.tsx'; +import csvImportSource from '../../../components/demos/CsvImportDemo.tsx?raw'; +--- + + +

CSV import

+ +

+ Since data is just an array, importing is not a table feature to configure — + it's reading a file, parsing it, and calling setData. Upload a file below, or + load the sample: +

+ + + + + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

+ +

Read the file

+ +

+ Use FileReader to read the uploaded file as text: +

+ + setData(parseCsv(String(reader.result))); + reader.readAsText(file); +} + + e.target.files?.[0] && handleFile(e.target.files[0])} />`} /> + +

Parse rows into your row type

+ +

+ A minimal parser is a few lines — split on newlines, split each line on commas, and zip + against the header row. It doesn't handle quoted commas; reach for a library like + papaparse if your CSVs need that: +

+ + h.trim()); + + return lines.filter(Boolean).map(line => { + const cells = line.split(',').map(c => c.trim()); + return Object.fromEntries(headers.map((h, i) => [h, cells[i] ?? ''])) as unknown as Contact; + }); +}`} /> + +

Pass it straight to data

+ +

No import prop, no adapter — the parsed array is the table's data:

+ + ([]); + +`} /> +
diff --git a/apps/docs/src/pages/docs/recipes/editable-grid.astro b/apps/docs/src/pages/docs/recipes/editable-grid.astro index 946f3121..5b5ea8cf 100644 --- a/apps/docs/src/pages/docs/recipes/editable-grid.astro +++ b/apps/docs/src/pages/docs/recipes/editable-grid.astro @@ -1,7 +1,9 @@ --- import DocsLayout from '../../../layouts/DocsLayout.astro'; import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; import UrlSyncDemo from '../../../components/demos/UrlSyncDemo.tsx'; +import urlSyncSource from '../../../components/demos/UrlSyncDemo.tsx?raw'; --- @@ -18,9 +20,17 @@ import UrlSyncDemo from '../../../components/demos/UrlSyncDemo.tsx'; Copy the URL and open it in a new tab.

-
+ -
+ + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

Reading initial state from the URL

diff --git a/apps/docs/src/pages/docs/recipes/inventory-editor.astro b/apps/docs/src/pages/docs/recipes/inventory-editor.astro new file mode 100644 index 00000000..b29f52fb --- /dev/null +++ b/apps/docs/src/pages/docs/recipes/inventory-editor.astro @@ -0,0 +1,88 @@ +--- +import DocsLayout from '../../../layouts/DocsLayout.astro'; +import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; +import EditableGridDemo from '../../../components/demos/EditableGridDemo.tsx'; +import editableGridSource from '../../../components/demos/EditableGridDemo.tsx?raw'; +--- + + +

Editable inventory grid

+ +

+ A line-item grid you edit directly, like a lightweight spreadsheet: product name and quantity + are editable inline, a total column is derived automatically, and a footer + bar keeps a running grand total. No form modal, no separate edit view. +

+ + + + + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

+ +

Editable columns with a shared handler

+ +

+ Set editable per column and point every editable column at the same + onCellEdit handler. Branch on the column's id to validate and + coerce the incoming value: +

+ + ) { + const field = column.id as keyof LineItem; + setData(prev => prev.map(r => { + if (r.id !== row.id) return r; + if (field === 'quantity') return { ...r, quantity: Math.max(0, parseInt(value) || 0) }; + if (field === 'price') return { ...r, price: Math.max(0, parseFloat(value) || 0) }; + return { ...r, [field]: value }; + })); +} + +const columns: TableColumn[] = [ + { id: 'product', name: 'Product', selector: r => r.product, editable: true, onCellEdit: handleCellEdit }, + { id: 'quantity', name: 'Qty', selector: r => r.quantity, editable: true, editor: { type: 'number' }, onCellEdit: handleCellEdit, right: true }, + { id: 'price', name: 'Unit price', selector: r => r.price, editable: true, editor: { type: 'number' }, onCellEdit: handleCellEdit, + format: r => \`$\${r.price.toFixed(2)}\`, right: true }, +];`} /> + +

A derived column that isn't editable

+ +

+ The total column has no editable flag — it's computed straight + from the row on every render, so it never drifts out of sync with quantity or price: +

+ + r.quantity * r.price, + format: r => \`$\${(r.quantity * r.price).toFixed(2)}\`, + right: true, +}`} /> + +

Running grand total

+ +

+ Reduce over data on every render and show it in a footer bar below the table — + it updates automatically as cells are edited, with no extra event wiring: +

+ + sum + r.quantity * r.price, 0); + +
+ +
Grand total: \${grandTotal.toFixed(2)}
+
`} /> + +

+ Prefer it inside the table itself? Pass a footer to a column instead — see + Footer for the built-in summary row. +

+
diff --git a/apps/docs/src/pages/docs/recipes/live-updates.astro b/apps/docs/src/pages/docs/recipes/live-updates.astro new file mode 100644 index 00000000..4dc75a1b --- /dev/null +++ b/apps/docs/src/pages/docs/recipes/live-updates.astro @@ -0,0 +1,74 @@ +--- +import DocsLayout from '../../../layouts/DocsLayout.astro'; +import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; +import LiveUpdatesDemo from '../../../components/demos/LiveUpdatesDemo.tsx'; +import liveUpdatesSource from '../../../components/demos/LiveUpdatesDemo.tsx?raw'; +--- + + +

Live-updating data grid

+ +

+ A price ticker where rows briefly highlight when their value changes — the same pattern behind + trading blotters and live ops dashboards. It's just three ingredients you already have: + keyField to identify rows across updates, a small piece of state tracking which + rows just changed, and conditionalRowStyles to flash them. +

+ + + + + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

+ +

Match rows across updates with keyField

+ +

+ When data is replaced on every tick, React (and the table) need a stable identity + per row. Set keyField to whichever field uniquely identifies a row: +

+ + `} /> + +

Track which rows just changed

+ +

+ Keep a Set of recently-changed keys in state. Add a key when its row updates, + then remove it after the flash duration: +

+ + >(new Set()); + +function updatePrice(symbol: string, next: number) { + setData(prev => prev.map(r => (r.symbol === symbol ? { ...r, price: next } : r))); + setFlashed(prev => new Set(prev).add(symbol)); + setTimeout(() => setFlashed(prev => { + const copy = new Set(prev); + copy.delete(symbol); + return copy; + }), 600); +}`} /> + +

Flash the row with conditionalRowStyles

+ +

+ No animation library needed — a CSS transition on the inline style fades the + highlight out on its own: +

+ + [] = [ + { + when: r => flashed.has(r.symbol), + style: { backgroundColor: '#fefce8', transition: 'background-color 0.6s ease' }, + }, +]; + +`} /> +
diff --git a/apps/docs/src/pages/docs/recipes/master-detail.astro b/apps/docs/src/pages/docs/recipes/master-detail.astro index 8b2dfbef..81580153 100644 --- a/apps/docs/src/pages/docs/recipes/master-detail.astro +++ b/apps/docs/src/pages/docs/recipes/master-detail.astro @@ -1,7 +1,9 @@ --- import DocsLayout from '../../../layouts/DocsLayout.astro'; import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; import DashboardDrilldownDemo from '../../../components/demos/DashboardDrilldownDemo.tsx'; +import dashboardDrilldownSource from '../../../components/demos/DashboardDrilldownDemo.tsx?raw'; --- @@ -10,13 +12,21 @@ import DashboardDrilldownDemo from '../../../components/demos/DashboardDrilldown

A department overview table that combines expandable rows, conditional row styles for health status, and custom cell - renderers for inline progress bars. Expand any row to drill into individual team member - utilization. + renderers for inline progress bars. Expand any row to drill into a card grid of individual + team members — the detail panel is a plain React component, not another table.

-
+ -
+ + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

Conditional row styles

@@ -30,17 +40,28 @@ import DashboardDrilldownDemo from '../../../components/demos/DashboardDrilldown { when: r => r.health === 'at-risk', style: { backgroundColor: '#fffdf0' } }, ];`} /> -

Nested table in the expander

+

A card layout in the expander, not a nested table

- Render a second DataTable inside expandableRowsComponent. + expandableRowsComponent can render anything — it doesn't have to be another + DataTable. A grid of team-member cards reads better here than a table-in-a-table. The parent row arrives as data:

) { return (
- +
+ {dept.members.map(member => ( +
+
{initials(member.name)}
+
+ {member.name} — {member.role} + +
+
+ ))} +
); } @@ -53,6 +74,28 @@ import DashboardDrilldownDemo from '../../../components/demos/DashboardDrilldown conditionalRowStyles={conditionalRowStyles} />`} /> +

Expand all / collapse all

+ +

+ expandableRowExpanded re-syncs whenever its result changes, so a single boolean + in state — flipped by the "Expand all" button above the demo — expands or collapses every + department at once: +

+ + setAllExpanded(v => !v)}> + {allExpanded ? 'Collapse all' : 'Expand all'} + + + allExpanded} +/>`} /> +

Inline progress bars in cells

diff --git a/apps/docs/src/pages/docs/recipes/persist-column-widths.astro b/apps/docs/src/pages/docs/recipes/persist-column-widths.astro index 0ffd3736..3a32489c 100644 --- a/apps/docs/src/pages/docs/recipes/persist-column-widths.astro +++ b/apps/docs/src/pages/docs/recipes/persist-column-widths.astro @@ -1,7 +1,9 @@ --- import DocsLayout from '../../../layouts/DocsLayout.astro'; import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; import PersistColumnWidthsDemo from '../../../components/demos/PersistColumnWidthsDemo.tsx'; +import persistColumnWidthsSource from '../../../components/demos/PersistColumnWidthsDemo.tsx?raw'; --- @@ -14,9 +16,17 @@ import PersistColumnWidthsDemo from '../../../components/demos/PersistColumnWidt to localStorage, a database, or anywhere else.

-
+ -
+ + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

localStorage

diff --git a/apps/docs/src/pages/docs/recipes/row-grouping.astro b/apps/docs/src/pages/docs/recipes/row-grouping.astro index e2730d85..4986ae00 100644 --- a/apps/docs/src/pages/docs/recipes/row-grouping.astro +++ b/apps/docs/src/pages/docs/recipes/row-grouping.astro @@ -1,7 +1,9 @@ --- import DocsLayout from '../../../layouts/DocsLayout.astro'; import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; import InlineRowActionsDemo from '../../../components/demos/InlineRowActionsDemo.tsx'; +import inlineRowActionsSource from '../../../components/demos/InlineRowActionsDemo.tsx?raw'; --- @@ -13,9 +15,17 @@ import InlineRowActionsDemo from '../../../components/demos/InlineRowActionsDemo call needed to see the result.

-
+ -
+ + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

The actions column

diff --git a/apps/docs/src/pages/docs/recipes/row-groups.astro b/apps/docs/src/pages/docs/recipes/row-groups.astro new file mode 100644 index 00000000..e90ee083 --- /dev/null +++ b/apps/docs/src/pages/docs/recipes/row-groups.astro @@ -0,0 +1,111 @@ +--- +import DocsLayout from '../../../layouts/DocsLayout.astro'; +import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; +import RowGroupingDemo from '../../../components/demos/RowGroupingDemo.tsx'; +import rowGroupingSource from '../../../components/demos/RowGroupingDemo.tsx?raw'; +--- + + +

Grouped rows with aggregates

+ +

+ Row grouping with per-group totals is usually sold as an enterprise grid feature. Here it's a + plain array reduce plus two props you already know: expandableRows + to drill into a group's line items, and a column footer for the grand total. The + detail panel is a plain list, not a table nested inside a table. +

+ + + + + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

+ +

Group the flat data yourself

+ +

+ No groupBy prop to learn — reduce your flat rows into per-group summary objects + before they ever reach the table: +

+ + >((acc, order) => { + const g = (acc[order.region] ??= { region: order.region, count: 0, total: 0, orders: [] }); + g.count += 1; + g.total += order.amount; + g.orders.push(order); + return acc; + }, {}), +);`} /> + +

Drill into a group without nesting another table

+ +

+ Each summary row already carries its own orders, so the expander component needs no extra + data fetching. It doesn't need to be a DataTable either — a simple line-item + list reads more clearly than a table nested inside a table: +

+ + ) { + return ( +
+ {data.orders.map(order => ( +
+ {order.customer} + \${order.amount.toLocaleString()} +
+ ))} +
+ ); +} + +`} /> + +

Expand all / collapse all

+ +

+ Same pattern as any other expandable table — one boolean in state, flipped by the + button above the demo, expands or collapses every region at once: +

+ + allExpanded} +/>`} /> + +

Per-group and grand totals with footer

+ +

+ The same footer function that produces a table-wide total also works on grouped + data — it receives whatever rows are currently visible: +

+ + r.total, + format: r => \`$\${r.total.toLocaleString()}\`, + footer: (rows: RegionGroup[]) => \`$\${rows.reduce((sum, r) => sum + r.total, 0).toLocaleString()}\`, +}`} /> + +

See Footer for more on static labels vs. computed aggregates.

+
diff --git a/apps/docs/src/pages/docs/recipes/server-side.astro b/apps/docs/src/pages/docs/recipes/server-side.astro index a05bf091..c04d07c4 100644 --- a/apps/docs/src/pages/docs/recipes/server-side.astro +++ b/apps/docs/src/pages/docs/recipes/server-side.astro @@ -1,7 +1,9 @@ --- import DocsLayout from '../../../layouts/DocsLayout.astro'; import CodeBlock from '../../../components/CodeBlock.astro'; +import Demo from '../../../components/Demo.astro'; import ServerSideRecipeDemo from '../../../components/demos/ServerSideRecipeDemo.tsx'; +import serverSideSource from '../../../components/demos/ServerSideRecipeDemo.tsx?raw'; --- @@ -13,9 +15,17 @@ import ServerSideRecipeDemo from '../../../components/demos/ServerSideRecipeDemo The demo below simulates a 400ms API delay — filter by name or department to see it in action.

-
+ -
+ + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

@@ -13,9 +15,17 @@ import AuditLogDemo from '../../../components/demos/AuditLogDemo.tsx'; search that filters across multiple fields at once.

-
+ -
+ + +

+ This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. +

Fixed header with capped scroll height

diff --git a/apps/docs/src/pages/docs/sorting.astro b/apps/docs/src/pages/docs/sorting.astro index 5cdc119b..f79d15d4 100644 --- a/apps/docs/src/pages/docs/sorting.astro +++ b/apps/docs/src/pages/docs/sorting.astro @@ -497,4 +497,9 @@ function App() { ['ref.clearSort()', 'DataTableHandle', '-', 'Imperatively reset sort to the default (defaultSortFieldId / defaultSortAsc), or unsorted if no defaults are set. See DataTableHandle.'], ]} /> + +

+ See it combined with other features in the Server-side sort, page & filter recipe + and URL-synced table state. +

diff --git a/apps/docs/src/pages/index.astro b/apps/docs/src/pages/index.astro index e1d4d049..3f04e6cf 100644 --- a/apps/docs/src/pages/index.astro +++ b/apps/docs/src/pages/index.astro @@ -240,7 +240,7 @@ export default function App() { ['Styled table out of the box', 'You build the UI', '✓', '✓', '✓'], ['Sorting, pagination & selection UI', 'State only', '✓', '✓', '✓'], ['Expandable row panels', 'State only', 'Enterprise tier', 'Pro tier', '✓'], - ['Dark mode & themes', 'Bring your own', '✓ built-in', 'Via MUI theme', '5 built-in'], + ['Dark mode & themes', 'Bring your own', '✓ built-in', 'Via MUI theme', '5 built-in, incl. Material'], ['Requires a UI framework', 'No', 'No', '@mui/material + Emotion', 'No'], ['Size (min+gzip)', '~15 KB', '~338 KB', '~117 KB + MUI', '~35 KB'], ['Paid tiers', 'None', 'Enterprise', 'Pro & Premium', 'None'],