Skip to Content
ComponentsDataTable

DataTable

Easy to use table and datagrids built using TanStack Table.

Documentation

Install the required peer dependencies to start using this component:

npm install @tanstack/react-table

Use the table prop to pass a table instance returned from useReactTable() hook. At minimum make sure to include columns, data, and getCoreRowModel when using the hook.

ArthurMorgansuccess$20
JohnMarstonprocessing$12.75
MicahBellfailed$1.25
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), }); return ( <DataTable table={table}> <DataTableBody /> </DataTable> ); }
import { DataTable, DataTableBody, DataTableFooter } from "@optiaxiom/react"; export default () => ( <DataTable> <DataTableBody> <DataTableAction /> <DataTableCheckbox /> <DataTableLabel /> </DataTableBody> <DataTableFooter /> </DataTable> );

Headers in DataTable are sticky by default. Set a height on the component to allow scrolling vertically when a large number of rows are present.

1EdgardoLubowitz$696.47
2HenriPaucek-Kshlerin$719.47
3EverardoKessler$392.12
4GranvilleDeckow$59.67
5JustynNicolas$175.45
6ModestaReichert$849.44
7FrancescaGreenholt$322.96
8AudreanneKeeling$630.98
9HerminioKautzer$493.69
10TyreekMuller$893.39
11EmiliaStreich$115.61
12JaymeLeannon-Moore$250.45
13ReymundoLittle$120.62
14ElliotParker$342.76
15JohnathonMcGlynn$875.46
16MargueriteBoehm$624.90
17DedrickMarquardt$763.69
18SheaReilly$95.71
19LaurettaCronin$16.12
20MauricePouros$153.07
21GilesSpinka$554.38
22AltaJacobi$357.40
23ZeldaRuecker$704.96
24MathewJacobi$593.18
25EverettOrn$240.85
26BrendaOkuneva$105.90
27KayHowe$846.51
28FidelSimonis$316.79
29KatrineLebsack$338.67
30YadiraDurgan$2.68
31JosieWyman$292.49
32KendraHuels$257.54
33CarolineStreich$731.08
34ArjunDoyle$983.53
35KatelinGulgowski$450.64
36KeshawnRomaguera$927.59
37AmaliaSmitham$741.86
38OscarHaag$165.93
39BerryTorphy$665.26
40IgnatiusRunolfsdottir$696.31
41ArvelSchultz$565.64
42TimmyMarvin$337.06
43ArielleSchuster$751.65
44BrantConn$909.88
45HermanCartwright$399.38
46RandallSchowalter$201.40
47KatlynnMcCullough$7.42
48MikelOrtiz$206.09
49LottieRodriguez$29.32
50BobbyBergstrom$472.91
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), state: { pagination: { pageIndex: 0, pageSize: data.length } }, }); return ( <DataTable maxH="xs" table={table}> <DataTableBody /> </DataTable> ); }

Follow the column pinning guide and use the columnPinning option to pin columns either to the left or right side of the table.

1Granville.DeckowDaniella98@gmail.comJaydenBeierRegional Tactics Producer$696.47
2Herminio_KautzerModesta.Reichert36@yahoo.comEldoraKundeInvestor Security Administrator$634.40
3Evelyn_JohnsonEmilia48@gmail.comJosianneLittleCustomer Research Assistant$623.95
4Aglae15Linnie.Spinka76@hotmail.comClementineReillyCustomer Security Agent$669.32
5Laura.CorwinKayden_Walsh35@yahoo.comEllisRueckerRegional Assurance Strategist$318.76
6Eula0Benjamin_Hand@hotmail.comRubyeSimonisRegional Paradigm Director$513.13
7Wilmer.KeeblerElaina_Turcotte@gmail.comRaeStreichRegional Mobility Officer$905.35
8Malvina_OndrickaTimmy_Koch74@gmail.comMelodyHaagInternal Directives Consultant$93.32
9Tad.BodeKennedy33@yahoo.comNestorSchusterCustomer Mobility Associate$438.21
10Aletha_Bartoletti47Colleen_Koss@gmail.comToniOrtizCustomer Implementation Developer$562.22
11Jayden_HammesMadalyn.Runte-Marvin@gmail.comAmaniJenkinsForward Operations Administrator$542.64
12Alphonso_Wuckert62Teresa.Bosco50@gmail.comAlyshaTremblayFuture Security Manager$989.04
13Vivian.StreichGarret34@yahoo.comPinkDickiDynamic Division Developer$484.91
14Carolyne2Daryl.Emard23@yahoo.comLorenzoLeschFuture Identity Supervisor$67.44
15Trisha.Stamm59Orin87@gmail.comVeronaCormierDistrict Accountability Director$16.11
16Tony_White50Arturo.Swift@gmail.comDaneKlingCustomer Branding Specialist$721.19
17Fay24Clovis.Jaskolski85@yahoo.comThaddeusHarveyInternational Interactions Orchestrator$480.89
18Elise_BoscoLori32@hotmail.comRemingtonCarrollInternational Usability Administrator$908.02
19Cole_Kohler23Cruz_Robel@yahoo.comMattieSanfordChief Intranet Manager$191.33
20Forest.JonesOllie80@hotmail.comAdaGloverSenior Tactics Representative$953.70
21Leopoldo.CummerataEmelia_Baumbach@yahoo.comAlfredaHillsLead Tactics Representative$279.80
22Christine.Brakus-MullerRay_Witting@yahoo.comGussieJacobiFuture Security Engineer$296.90
23Adonis_QuitzonAkeem.Stracke52@gmail.comAugustineFramiInternal Group Technician$868.32
24Alisha64Mack.Ryan@yahoo.comDavonBauch-O'ConnellInternational Tactics Engineer$542.86
25Hassie.Effertz85Eldridge_Grant@gmail.comYadiraTillman-VonRuedenHuman Integration Supervisor$678.10
26Graham.Morissette99Hershel80@hotmail.comTheodoreWatsicaProduct Tactics Planner$893.87
27Mona53Cale3@gmail.comLewisMaggioSenior Branding Coordinator$220.60
28Gerry.OConnerLizzie_Lockman56@yahoo.comJohnathonLeschLegacy Research Assistant$359.87
29Royal60Alfonso.Schinner44@hotmail.comEddStokesPrincipal Division Agent$875.77
30Alexandria_QuigleyJorge.Flatley@gmail.comBonitaBarrowsGlobal Division Technician$715.75
31Carolyne_Rolfson-LindgrenDameon_Braun@hotmail.comVivianUptonLead Marketing Specialist$625.91
32Ayden_Welch1Ericka35@hotmail.comJairoDietrichInternational Tactics Strategist$656.34
33Carter_Farrell-McKenzieJacinto11@yahoo.comRahsaanSchroederCorporate Implementation Technician$801.28
34Ardella94Norval_Abbott49@hotmail.comFreedaSporerForward Markets Consultant$584.91
35Effie6Cora.Kuhic@gmail.comSaraiWestPrincipal Integration Orchestrator$443.60
36Raphaelle_Brakus71Corine17@hotmail.comLillianaLubowitzLead Quality Officer$994.62
37Libby53Anthony.Powlowski@gmail.comSheridanRunteLegacy Implementation Engineer$971.37
38Linwood_Wolf54Nico.Howe65@yahoo.comNickolasKeelingDirect Accounts Planner$947.07
39Jonathon92Mabelle85@gmail.comFelipaTrompFuture Research Strategist$904.12
40Kayley_Stamm55Hector_DuBuque48@gmail.comBarbaraHowellGlobal Tactics Coordinator$841.17
41Cleve.Wolff67Matteo.Kerluke96@hotmail.comJazmyneBoehmDynamic Response Assistant$990.24
42Jarod_McDermott66Terrill_Weissnat@hotmail.comDelphineRolfsonDynamic Implementation Representative$723.47
43Waldo.SchusterJacklyn8@gmail.comJulietPacochaCustomer Markets Technician$15.19
44Milford74Lester.Mertz23@yahoo.comFreddieRosenbaumInvestor Tactics Specialist$101.74
45Damian11Mossie.Lynch19@hotmail.comArnaldoTreutelSenior Configuration Coordinator$369.04
46Alana.KassulkeRyder45@yahoo.comMaudKlingHuman Mobility Designer$932.09
47Myron.HermanGene.Bechtelar1@yahoo.comEulahRitchieInternal Division Supervisor$39.91
48Norberto.KemmerFrankie45@hotmail.comFrankAnkundingFuture Tactics Director$727.19
49Jayne_Hagenes31Gunnar.Mueller@hotmail.comAgustinHansenCentral Integration Administrator$345.90
50Jerod.Nikolaus3Asha_Beer@yahoo.comDiegoBodeInternational Tactics Supervisor$290.65
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), state: { columnPinning: { left: ["id", "username"], }, pagination: { pageIndex: 0, pageSize: data.length }, }, }); return ( <DataTable maxH="xs" table={table}> <DataTableBody /> </DataTable> ); }

Follow the row selection guide to allow selecting and highlighting rows.

Use the DataTableCheckbox component to automatically attach selection handlers and make the whole row selectable.

Combine Checkbox/DataTableCheckbox with Cover to make them easier to interact with by spanning the whole cell.

Finally, make sure to use the DataTableLabel in your primary cell to set the label of the row and the checkbox for screen readers.

Check to select1
Diane Reichel
$696.47
Check to select2
Jeffery Heathcote
$980.77
Check to select3
Jaime Deckow
$438.57
Check to select4
Lyle Reilly
$531.55
Check to select5
Harriet Greenholt
$722.45
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), }); return ( <DataTable table={table}> <DataTableBody /> </DataTable> ); }

Use the DataTableAction component to show links, buttons, or dropdown menus within rows that are only visible when the user hovers over the row.

You can enable the primary prop on any one of those actions to mark that as the primary action. This will automatically enable keyboard navigation within the table:

  • Double click on row to perform primary action.
  • Use up/down/left/right arrow keys to navigate between rows and actions.

Finally, make sure to use the DataTableLabel in your primary cell to set the label of the row for screen readers.

Name
Oct 30, 2025
Diane Reichel
Oct 30, 2025
Ira Jacobi
Oct 30, 2025
Omar Reichert
Oct 29, 2025
Jennie Kautzer
Oct 30, 2025
Florence Streich
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), }); return ( <DataTable table={table}> <DataTableBody /> </DataTable> ); }

DataTable supports both client-side and server-side pagination. Follow the pagination guide to learn how to implement pagination with TanStack Table.

Simply provide the getPaginationRowModel and pagination options to use client-side pagination.

1EdgardoLubowitz$696.47
2HenriPaucek-Kshlerin$719.47
3EverardoKessler$392.12
4GranvilleDeckow$59.67
5JustynNicolas$175.45
6ModestaReichert$849.44
7FrancescaGreenholt$322.96
8AudreanneKeeling$630.98
9HerminioKautzer$493.69
10TyreekMuller$893.39

1 - 10 of 50

"use client"; import { DataTable, DataTableBody, DataTableFooter } from "@optiaxiom/react"; import { getCoreRowModel, getPaginationRowModel, useReactTable, } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), initialState: { pagination: { pageIndex: 0, pageSize: 10 } }, }); return ( <DataTable maxH="xs" table={table}> <DataTableBody /> <DataTableFooter /> </DataTable> ); }

To implement manual server-side pagination we can use manualPagination and rowCount. Then combine it with onPaginationChange and state.pagination to control pagination and react to changes.

1EdgardoLubowitz$696.47
2HenriPaucek-Kshlerin$719.47
3EverardoKessler$392.12
4GranvilleDeckow$59.67
5JustynNicolas$175.45
6ModestaReichert$849.44
7FrancescaGreenholt$322.96
8AudreanneKeeling$630.98
9HerminioKautzer$493.69
10TyreekMuller$893.39

1 - 10 of 50

"use client"; import { DataTable, DataTableBody, DataTableFooter } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { useMemo, useState } from "react"; import { columns } from "./columns"; import { data } from "./data"; /** * Simulate fetching data from server. */ const fetchData = ({ pageIndex = 0, pageSize = 10 }) => data.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize); export function App() { const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); const table = useReactTable({ columns, data: useMemo(() => fetchData(pagination), [pagination]), getCoreRowModel: getCoreRowModel(), manualPagination: true, onPaginationChange: setPagination, rowCount: data.length, state: { pagination }, }); return ( <DataTable maxH="xs" table={table}> <DataTableBody /> <DataTableFooter /> </DataTable> ); }

Set the loading prop to true to show a loading skeleton while your table data is loading.

"use client"; import { DataTable, DataTableBody, Flex, Switch } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { useState } from "react"; import { columns } from "./columns"; export function App() { const [data] = useState([]); const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), }); const [loading, setLoading] = useState(true); return ( <Flex alignItems="start" maxW="full"> <Switch checked={loading} onCheckedChange={setLoading}> Loading </Switch> <DataTable maxH="xs" table={table}> <DataTableBody loading={loading} /> </DataTable> </Flex> ); }

Alternatively you can provide an object to the loading prop to denote individual loading state of sub rows.

1EdgardoLubowitz$696.47
2HenriPaucek-Kshlerin$719.47
3EverardoKessler$392.12
"use client"; import { DataTable, DataTableBody, Flex, Switch } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { useState } from "react"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.id.toString(), }); const [loading, setLoading] = useState<Record<string, "sub-rows" | boolean>>({ "1": "sub-rows", }); return ( <Flex alignItems="start" maxW="full"> <Switch checked={loading["1"] === "sub-rows"} onCheckedChange={(checked) => setLoading({ "1": checked ? "sub-rows" : false }) } > Loading </Switch> <DataTable maxH="xs" table={table}> <DataTableBody loading={loading} /> </DataTable> </Flex> ); }

DataTable supports both client-side and server-side sorting. Follow the sorting guide to learn how to implement sorting with TanStack Table.

Simply provide getSortedRowModel on table and optionally use enableSorting to control which columns you’d like to be sortable to use client-side sorting.

ID
1EdgardoLubowitz$696.47
2HenriPaucek-Kshlerin$719.47
3EverardoKessler$392.12
4GranvilleDeckow$59.67
5JustynNicolas$175.45
6ModestaReichert$849.44
7FrancescaGreenholt$322.96
8AudreanneKeeling$630.98
9HerminioKautzer$493.69
10TyreekMuller$893.39
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), }); return ( <DataTable maxH="xs" table={table}> <DataTableBody /> </DataTable> ); }

To implement manual server-side sorting we can use manualSorting. Then combine it with onSortingChange and state.sorting to control sorting and react to changes.

ID
8AudreanneKeeling$630.98
1EdgardoLubowitz$696.47
3EverardoKessler$392.12
7FrancescaGreenholt$322.96
4GranvilleDeckow$59.67
2HenriPaucek-Kshlerin$719.47
9HerminioKautzer$493.69
5JustynNicolas$175.45
6ModestaReichert$849.44
10TyreekMuller$893.39
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, type SortingState, useReactTable, } from "@tanstack/react-table"; import { useMemo, useState } from "react"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const [sorting, setSorting] = useState([ { desc: false, id: "firstName", }, ]); const table = useReactTable({ columns, data: useMemo(() => sortData(sorting), [sorting]), getCoreRowModel: getCoreRowModel(), onSortingChange: setSorting, state: { sorting }, }); return ( <DataTable maxH="xs" table={table}> <DataTableBody /> </DataTable> ); } /** * Simulate sorting data from server. */ const sortData = (sorting: SortingState) => data.toSorted((a, b) => { for (const state of sorting) { if ( !( state.id === "amount" || state.id === "firstName" || state.id === "lastName" ) ) { return 0; } const result = (state.desc ? -1 : 1) * a[state.id].localeCompare(b[state.id], undefined, { numeric: true }); if (result !== 0) { return result; } } return 0; });

Follow the expanding guide to allow expanding and collapsing rows.

Use the DataTableExpandableHeader and DataTableExpandableCell components to render columns with built-in buttons for toggling rows.

Finally, combine it with the loading prop to show a loading state while expanded rows are being fetched.

1
Clothing
$1415.94
2
Books
$919.50
"use client"; import { DataTable, DataTableBody } from "@optiaxiom/react"; import { getCoreRowModel, getExpandedRowModel, useReactTable, } from "@tanstack/react-table"; import { columns } from "./columns"; import { data } from "./data"; export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), getExpandedRowModel: getExpandedRowModel(), getSubRows: (row) => row.items, }); return ( <DataTable table={table}> <DataTableBody /> </DataTable> ); }

Table automatically uses virtualized rendering when rows and columns cross a certain threshold to improve rendering performance.

Columns will be virtualized when there are more than 20 columns in a table and rows will be virtualized when there are more than 20 rows rendered or if columns are also being virtualized.

Timer: 0s (to simulate re-rendering)

"use client"; import { faker } from "@faker-js/faker"; import { DataTable, DataTableBody, Flex, Text } from "@optiaxiom/react"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { useInViewTimer } from "./useInViewTimer"; faker.seed(123); const cols = 50; const rows = 500; const data = Array.from({ length: rows }, () => Object.fromEntries( Array.from({ length: cols }, (_, index) => [ `col${index}`, faker.book.title(), ]), ), ); const columns = Array.from({ length: cols }, (_, index) => ({ accessorKey: `col${index}`, header: `Column ${index + 1}`, })); export function App() { const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel(), state: { pagination: { pageIndex: 0, pageSize: data.length } }, }); /** * A simple hook to re-render the table when it is visible on the screen. */ const [count, ref] = useInViewTimer(); return ( <Flex maxW="full" ref={ref}> <Text>Timer: {count}s (to simulate re-rendering)</Text> <DataTable maxH="sm" table={table}> <DataTableBody /> </DataTable> </Flex> ); }

Props

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

table*

Pass the table instance returned from useReactTable() hook.

Table<any>

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

estimatedRowHeight

The estimated height of rows in pixels when virtualization is enabled.

number

Default: 52

loading

Whether to show skeleton for the whole table or specific rows.

false | true | Record<string, boolean | "sub-rows">

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

pageSizeOptions

Specify a custom set of page size options.

{ label: string; value: string; }[]

Default: ["10", "20", "30", "50", "100"].map((size) => ({ label: size, value: size, }))

showPageSizeOptions

Whether to show a dropdown that allows controlling how many rows to show per page.

false | true

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

primary

Whether this is the primary action within the row.

false | true

visible

Control whether to always show the contents or only when user is hovering or interacting with them.

"always" | "if-needed"

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

description

Add secondary text after the label.

ReactNode

indeterminate

Display a partially checked icon instead of the regular checkmark.

false | true

onCheckedChange

Handler that is called when the checked state changes.

(checked: boolean) => void

visible

Control whether to always show the contents or only when user is hovering or interacting with them.

"always" | "if-needed"

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

Changelog

  • Added DataTableAction component
  • Added DataTableCheckbox component
  • Added DataTableLabel component
  • Moved component out of Alpha.

    // Before import { DataTable } from "@optiaxiom/react/unstable"; // After import { DataTable } from "@optiaxiom/react";
  • Added component
Last updated on