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.

1LeahWuckert$696.47
2GeraldineBeier$684.83
3ClaytonNicolas$398.04
4MichelFarrell$849.44
5HomerKautzer$293.71
6TyrellBuckridge$312.26
7DelphineLeannon-Moore$317.28
8KarianneJohnson$120.62
9MauricePadberg$681.30
10DeclanBoyer$842.35
11AdrianaCronin$885.33
12MosesWalsh$153.07
13EthelZiemann$841.67
14MarkusFisher$355.91
15AylinOkuneva$343.45
16GersonD'Amore$846.51
17LoisYundt$829.12
18KristaGerhold-Maggio$905.35
19CarliWyman$806.97
20JoanneGulgowski$79.36
21PhilipQuitzon$927.59
22EdwinaBrown$839.25
23IlianaMann$664.87
24EugeniaMarvin$84.90
25RavenBode$751.65
26LeticiaSchroeder$138.41
27JuliusDuBuque$467.99
28AlecRodriguez$717.76
29ArleneRunte-Marvin$472.91
30AlveraKautzer$102.63
31ClaraDibbert$788.19
32TheodoreKohler$417.29
33DonnaSchowalter$502.22
34VirginiaAbshire$894.98
35BobHeaney-Schmeler$484.91
36MonaWisoky$798.85
37DougZemlak$650.75
38BlancaNicolas$236.46
39GwendolynMetz$281.23
40SpencerSanford$293.15
41SaraiKeeling-Swaniawski$45.89
42TonyCasper$355.96
43AdalineSwift$596.49
44DarrenGusikowski$363.57
45KristianKutch$418.63
46ClevelandHintz$3.53
47MargaretteSchulist$857.16
48OliveWehner$335.54
49DonnellHansen$908.02
50VernieCarroll$882.28
"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.

1Leilani.HettingerMonique34@gmail.comIrmaNicolasLegacy Directives Officer$696.47
2Laverna68Jeannette_Trantow@hotmail.comJanieMooreHuman Functionality Analyst$92.10
3Moses_Walsh35Mallory.Runolfsdottir@yahoo.comLilianCreminLegacy Solutions Planner$510.42
4Emanuel.SimonisFlavio.Pouros39@hotmail.comMaryannLueilwitzDirect Research Director$304.77
5Laverne45Corbin_Turcotte@gmail.comOtisKeeblerCorporate Communications Facilitator$521.53
6Nasir82Carolyne_Haag@gmail.comIlianaSchultzDistrict Accounts Planner$741.86
7Perry65Jeanne46@gmail.comTeriBarrowsCustomer Implementation Developer$128.63
8Ivan.Beahan9Maddison.Leannon@hotmail.comFayeVonInternal Directives Administrator$769.40
9Vicki.Bailey50Virginia.Abshire@yahoo.comVernaRauNational Data Consultant$807.79
10Ally42Blanca.Nicolas36@gmail.comCharlotteArmstrongCustomer Response Orchestrator$993.04
11Jodi_White50Reyes.Metz72@yahoo.comSherriOkunevaNational Operations Planner$355.96
12Malcolm88Jana68@yahoo.comEthelynWehnerNational Markets Producer$480.89
13Darian46Brenda_Bosco@yahoo.comCristianPowlowskiFuture Mobility Designer$709.63
14Dwayne.VandervortNya_Kovacek67@gmail.comToniErdmanCentral Functionality Coordinator$237.48
15Bernice.Jacobi95Tonya.Moore@gmail.comEdmundJast-O'ReillyChief Creative Director$444.22
16Jamarcus23Shelley_VonRueden@hotmail.comRuthBaileyRegional Branding Representative$625.21
17Braxton98Nelson.Zulauf64@gmail.comBillKleinInvestor Mobility Technician$134.61
18Jason.Zemlak22Gilbert.Trantow42@hotmail.comRoderickKiehnHuman Factors Specialist$919.48
19Julian_Dare75Allen_Beer37@gmail.comMajorSchmelerFuture Operations Designer$147.72
20Rosendo60Devin_Rowe@gmail.comRachaelKemmer-TorpCentral Division Specialist$817.10
21Darrell47Deangelo.Jacobson@gmail.comAlfredKossLead Quality Developer$715.75
22Maci_Welch62Sydni_Rolfson-Lindgren@gmail.comErickaKoepp-PaucekCorporate Applications Engineer$31.66
23Korbin.BreitenbergFreddie_Buckridge80@yahoo.comNikolasMcGlynnInternal Directives Strategist$225.53
24Zetta29Randall.Blick46@hotmail.comIrwinRyanDirect Implementation Engineer$328.33
25Mayra_Gottlieb-Bernier69Zella_Mertz66@yahoo.comAliyahStokesInternational Implementation Supervisor$15.01
26Philip_McGlynnCordie_Runte@hotmail.comStewartGoyetteChief Interactions Producer$338.58
27Freda.Spencer41Clayton_OHara22@yahoo.comFeltonLangworthFuture Research Strategist$554.41
28Vicki.ErnserBeverly.Hudson@yahoo.comTheresiaPollichGlobal Functionality Strategist$205.35
29Kolby.FisherDimitri.Reilly91@gmail.comFloydKunde-RolfsonInvestor Functionality Manager$83.34
30Jarret_Hickle32Van87@yahoo.comRavenMorarLead Branding Technician$393.42
31Damaris72Joey42@yahoo.comFloydHoppeDynamic Directives Executive$751.12
32Geoffrey33Maud_Powlowski@yahoo.comAlmaBatzGlobal Group Liaison$668.00
33Margarita.JohnsMose39@gmail.comBenjaminFriesenDirect Accountability Architect$458.82
34Valerie_BeerKrista_Lang@hotmail.comRoseHalvorsonProduct Applications Supervisor$497.47
35Jaqueline_OConnellErma_Green49@yahoo.comLindsayBernhardDirect Branding Director$264.40
36Sylvester0Jo.Dickinson17@yahoo.comAliviaHackettChief Integration Liaison$447.36
37Woodrow_Zemlak16Audrey62@yahoo.comWinifredArmstrongGlobal Data Supervisor$356.24
38Carole75Darrel.Marvin44@gmail.comAndresMarvinInternational Brand Designer$680.19
39Misty97Shawna.Crona@gmail.comMiaBruenLegacy Directives Director$957.85
40Jamison.BeahanMorris.Schmidt@yahoo.comRosemarieSwaniawskiDirect Factors Executive$673.63
41Misael86Aurelie_Raynor29@gmail.comBeverlyShanahanLead Response Officer$493.83
42German71Macey84@yahoo.comFlorianSpencerCentral Factors Consultant$193.32
43Harry.Crist59Nick.Witting61@yahoo.comNashQuitzonProduct Operations Officer$863.86
44Efren27Laron.Collier@yahoo.comLeilaniRau-SchowalterLegacy Infrastructure Supervisor$238.55
45Julio80Rupert.Huels86@gmail.comChaseSchimmelInvestor Mobility Officer$347.06
46Lora_Block67Jabari_Leannon5@gmail.comDellDaughertyLead Data Facilitator$704.51
47Daniel80Lindsey95@yahoo.comDonnaBergnaumDynamic Implementation Representative$828.99
48Amaya_Sipes92Sergio.Corkery54@gmail.comFernandoBlickRegional Data Director$711.01
49Alexanne8Roy91@gmail.comFabianCollierDistrict Marketing Analyst$937.37
50Mark75Itzel_Stamm53@hotmail.comJohannaDooleyForward Web Developer$3.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
Leah Kassulke MD
$696.47
Check to select2
Nadine Beier
$480.93
Check to select3
Mrs. Kristine Nicolas
$738.00
Check to select4
Desiree Murray
$722.45
Check to select5
Jeannette Kautzer DVM
$433.70
"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
Jul 10, 2026
Leah Kassulke MD
Jul 10, 2026
Nellie Daniel
Jul 10, 2026
Fred Greenholt
Jul 10, 2026
Sophie Kuvalis
Jul 10, 2026
Mrs. Josianne Cartwright
"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, enableRowSelection: false, 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.

1LeahWuckert$696.47
2GeraldineBeier$684.83
3ClaytonNicolas$398.04
4MichelFarrell$849.44
5HomerKautzer$293.71
6TyrellBuckridge$312.26
7DelphineLeannon-Moore$317.28
8KarianneJohnson$120.62
9MauricePadberg$681.30
10DeclanBoyer$842.35

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.

1LeahWuckert$696.47
2GeraldineBeier$684.83
3ClaytonNicolas$398.04
4MichelFarrell$849.44
5HomerKautzer$293.71
6TyrellBuckridge$312.26
7DelphineLeannon-Moore$317.28
8KarianneJohnson$120.62
9MauricePadberg$681.30
10DeclanBoyer$842.35

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, Group, 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 ( <Group alignItems="start" flexDirection="column" gap="16" maxW="full"> <Switch checked={loading} onCheckedChange={setLoading}> Loading </Switch> <DataTable maxH="xs" table={table}> <DataTableBody loading={loading} /> </DataTable> </Group> ); }

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

1LeahWuckert$696.47
2GeraldineBeier$684.83
3ClaytonNicolas$398.04
"use client"; import { DataTable, DataTableBody, Group, 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" | false>>({ "1": "sub-rows", }); return ( <Group alignItems="start" flexDirection="column" gap="16" 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> </Group> ); }

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
1LeahWuckert$696.47
2GeraldineBeier$684.83
3ClaytonNicolas$398.04
4MichelFarrell$849.44
5HomerKautzer$293.71
6TyrellBuckridge$312.26
7DelphineLeannon-Moore$317.28
8KarianneJohnson$120.62
9MauricePadberg$681.30
10DeclanBoyer$842.35
"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
3ClaytonNicolas$398.04
10DeclanBoyer$842.35
7DelphineLeannon-Moore$317.28
2GeraldineBeier$684.83
5HomerKautzer$293.71
8KarianneJohnson$120.62
1LeahWuckert$696.47
9MauricePadberg$681.30
4MichelFarrell$849.44
6TyrellBuckridge$312.26
"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, Group, 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 ( <Group flexDirection="column" gap="16" maxW="full" ref={ref}> <Text>Timer: {count}s (to simulate re-rendering)</Text> <DataTable maxH="sm" table={table}> <DataTableBody /> </DataTable> </Group> ); }

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

children

ReactNode

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

children

ReactNode

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, false | "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

children

ReactNode

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

children

ReactNode

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

children

ReactNode

className

string

description

Add secondary text after the label.

ReactNode

indeterminate

Display a partially checked icon instead of the regular checkmark.

false | true

name

The name of the form control element.

string

onCheckedChange

Handler that is called when the checked state changes.

(checked: boolean) => void

required

Whether selecting this input is required.

false | true

value

The value of the form control element.

string | number | readonly string[]

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

children

ReactNode

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