Data table
The slotsmith data table is a React table built on TanStack Table, with sorting, selection, pagination, tree rows, virtual rows and row reorder built in. Every part it renders is a slot: keep the fallbacks, or pass your design system’s own components.
Overview
The demo below uses the fallbacks only. Click a header to sort, shift-click a second header to sort by both, tick rows to select them, and change the page size under the table.
| Status | ||||
|---|---|---|---|---|
| Lena Park | Engineer | Platform | Active | |
| Omar Haddad | Designer | Product | Active | |
| Sofia Reyes | Engineer | Payments | Away | |
| Yuki Tanaka | Manager | Platform | Active | |
| Amir Nasser | Analyst | Growth | Active |
No rows selected.
import { useState } from "react";
import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";
import { people, type Person } from "../shared/people";
const columns: DataTableColumnDef<Person>[] = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "team", header: "Team" },
{ accessorKey: "status", header: "Status", enableSorting: false },
];
export default function Overview() {
const [selection, setSelection] = useState<Person[]>([]);
return (
<>
<DataTable
data={people}
columns={columns}
getRowId={(person) => person.id}
// Shift-click a second header to sort by both columns.
enableMultiSort
enableRowSelection
selection={selection}
onSelectionChange={setSelection}
defaultPagination={{ pageIndex: 0, pageSize: 5 }}
pageSizeOptions={[5, 10]}
/>
<p>{selection.length === 0 ? "No rows selected." : `Selected: ${selection.map((person) => person.name).join(", ")}`}</p>
</>
);
}import type { DataTableColumnDef } from "slotsmith/data-table";
export type Person = { id: string; name: string; role: string; team: string; status: string; joined: string };
export const people: Person[] = [
{ id: "1", name: "Lena Park", role: "Engineer", team: "Platform", status: "Active", joined: "2021-03-08" },
{ id: "2", name: "Omar Haddad", role: "Designer", team: "Product", status: "Active", joined: "2020-11-16" },
{ id: "3", name: "Sofia Reyes", role: "Engineer", team: "Payments", status: "Away", joined: "2022-01-24" },
{ id: "4", name: "Yuki Tanaka", role: "Manager", team: "Platform", status: "Active", joined: "2019-06-03" },
{ id: "5", name: "Amir Nasser", role: "Analyst", team: "Growth", status: "Active", joined: "2023-02-13" },
{ id: "6", name: "Chloe Martin", role: "Engineer", team: "Payments", status: "On leave", joined: "2021-09-27" },
{ id: "7", name: "Jonas Weber", role: "Support", team: "Care", status: "Active", joined: "2022-07-11" },
{ id: "8", name: "Priya Nair", role: "Designer", team: "Product", status: "Away", joined: "2020-04-20" },
{ id: "9", name: "Diego Alvarez", role: "Engineer", team: "Growth", status: "Active", joined: "2023-05-30" },
{ id: "10", name: "Hana Kim", role: "Manager", team: "Care", status: "Active", joined: "2018-10-01" },
{ id: "11", name: "Tomas Novak", role: "Analyst", team: "Payments", status: "On leave", joined: "2022-12-05" },
{ id: "12", name: "Nadia Farouk", role: "Support", team: "Care", status: "Active", joined: "2024-01-15" },
];
export const columns: DataTableColumnDef<Person>[] = [
{ accessorKey: "name", header: "Name" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "team", header: "Team" },
{ accessorKey: "status", header: "Status" },
{ accessorKey: "joined", header: "Joined" },
];Each feature has a guide with its own live demo: sorting and selection, pagination, server data with loading, empty and error states, tree rows, virtual rows for long lists, row reorder by pointer, touch or keyboard, footers, and the headless hook for your own markup.
Install
npm i slotsmith @tanstack/react-tablepnpm add slotsmith @tanstack/react-tableyarn add slotsmith @tanstack/react-tablebun add slotsmith @tanstack/react-tableVirtualDataTable from slotsmith/virtual also needs @tanstack/react-virtual.
import "slotsmith/data-table.css";Import the stylesheet once, at your app's entry. slotsmith/styles.css holds every component's styles instead.
Quick start
Pass data and columns. Columns are TanStack Table column definitions; getRowId gives each row a stable id.
| Things Fall Apart | Chinua Achebe | 1958 |
| Season of Migration to the North | Tayeb Salih | 1966 |
| The Left Hand of Darkness | Ursula K. Le Guin | 1969 |
| Beloved | Toni Morrison | 1987 |
| The Remains of the Day | Kazuo Ishiguro | 1989 |
| Wolf Hall | Hilary Mantel | 2009 |
import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";
import "slotsmith/data-table.css";
type Book = { id: string; title: string; author: string; year: number };
const books: Book[] = [
{ id: "1", title: "Things Fall Apart", author: "Chinua Achebe", year: 1958 },
{ id: "2", title: "Season of Migration to the North", author: "Tayeb Salih", year: 1966 },
{ id: "3", title: "The Left Hand of Darkness", author: "Ursula K. Le Guin", year: 1969 },
{ id: "4", title: "Beloved", author: "Toni Morrison", year: 1987 },
{ id: "5", title: "The Remains of the Day", author: "Kazuo Ishiguro", year: 1989 },
{ id: "6", title: "Wolf Hall", author: "Hilary Mantel", year: 2009 },
];
const columns: DataTableColumnDef<Book>[] = [
{ accessorKey: "title", header: "Title" },
{ accessorKey: "author", header: "Author" },
{ accessorKey: "year", header: "Year" },
];
export default function QuickStart() {
// Sorting is on for every column; pagination shows 10 rows a page by default.
return <DataTable data={books} columns={columns} getRowId={(book) => book.id} />;
}Guides
- Sorting and selectionSort the slotsmith data table by one or more columns, and select rows with checkboxes, controlled or uncontrolled, with a rule for rows that cannot be selected.
- PaginationSplit the slotsmith data table into pages, control the page and page size, change the page-size options, or hide the controls.
- Server dataPage and sort the slotsmith data table on a server with manualPagination, manualSorting and rowCount, and show loading, error and empty states.
- Tree rowsShow nested rows in the slotsmith data table with getSubRows, and control which rows are expanded.
- Virtual rowsRender long lists in the slotsmith data table with the virtual prop, which draws only the rows in view.
- Row reorderLet people drag rows of the slotsmith data table into a new order by pointer, touch or keyboard, in flat and tree tables, and store the order.
- Spreadsheet modeEdit the slotsmith data table like a spreadsheet: select cells, type over them, paste from Excel, drag the fill handle and undo.
- FootersAdd a footer row to the slotsmith data table, such as a total, from each column's footer definition.
- The headless hookRender the slotsmith data table in markup of your own with useDataTable: the state, the TanStack Table instance and row reorder, with no UI.
Reference
The API page is generated from the library’s source, so it lists every slot, prop and label the installed version has.
- SlotsEvery part you can replace, the props it receives and its fallback.
- PropsEvery prop, grouped, with its type and default.
- Labels & localesEvery string it shows or announces, and how to translate them.
- StylingIts class names and the tokens its stylesheet reads.
- AdaptersReady slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes.
Adapters
The table works with any design system. Each slot takes a component, so a library’s own table, checkbox, button and select can replace the fallbacks. The Adapters page has ready slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes, as examples of well-known libraries; the same approach works with your own.
Accessibility
The table renders a real <table>, so screen readers announce rows, columns and headers. What the fallbacks add:
- Sorting. A sortable header holds a
<button>, and its<th>carriesaria-sort(ascending,descendingornone). WithenableMultiSort, shift-click adds a column to the sort. See sorting and selection. - Selection. Each row and the header have a checkbox named by the
selectRowandselectAlllabels; the header checkbox is indeterminate while some rows on the page are selected. See sorting and selection. - Tree rows. The expand toggle is a
<button>witharia-expanded, named byexpandRoworcollapseRow. See tree rows. - Pagination. The controls sit in a
<nav>named by thepaginationlabel. The previous and next buttons are named bypreviousPageandnextPage, and the page counter is anaria-live="polite"region, so a page change is read out. The page-size control is the slotsmith Autocomplete, a combobox named by the visiblerowsPerPagetext. See pagination. - Loading and errors. While loading, the root has
aria-busy="true"and the skeleton rows are hidden from assistive technology. The error state hasrole="alert"and a retry button. See server data. - Row reorder. Each drag handle is a
<button>named byreorderRow, witharia-describedbypointing at hidden instructions (reorderInstructions) andaria-pressed="true"while it is lifted from the keyboard. A visually hidden live region (role="status",aria-live="assertive") reads each step with thereorderLifted,reorderMoved,reorderDroppedandreorderCancelledlabels, as positions such as “Position 2 of 5”. In a tree table, a row with no visible sibling has its handle disabled. See row reorder.
Moving a row from the keyboard, with focus on its drag handle:
| Keys | Action |
|---|---|
| SpaceEnter | Lift the row. |
| ↑↓ | Move the lifted row up or down one place among its siblings. |
| SpaceEnter | Drop the row in its new place. Focus stays on its handle. |
| Esc | Cancel. The row goes back where it was. |
| Tab | Cancel, and move focus on as usual. Leaving the handle any other way cancels too. |
Every label can be translated or replaced; see Labels & locales.
Limitations
FAQ
Does the data table support server-side pagination?
Yes. Set manualPagination, pass the current page as data and the total as rowCount, and fetch the next page in onPaginationChange. Server-side sorting works the same way with manualSorting.
Can I use virtual rows and row reorder together?
Yes. Pass virtual with useVirtualizer as use and set enableRowReorder: the dragged row stays rendered while it scrolls out of the window, the view scrolls under it near the edges, and the keyboard scrolls to the row it moves to.
Can I render custom cells?
Yes. Columns are TanStack Table column definitions, so a column's cell function can return any React node. To change the cell element itself, replace the Cell slot.
Which TanStack Table version does it need?
TanStack Table 9: install @tanstack/react-table ^9.2.0 next to slotsmith. Virtual rows also need @tanstack/react-virtual ^3.10.0, whose useVirtualizer you pass in as virtual.use.