A powerful and flexible data table component for displaying, filtering, sorting, and paginating tabular data.
Installation
Install the main component and dependencies:
pnpm dlx shadcn@latest add "@diceui/data-table"Filter and sort menu installs bundle a sortable component. Primitives come from Base UI.
Wrap your application with the NuqsAdapter for query state management:
import { NuqsAdapter } from "nuqs/adapters/next/app";
<NuqsAdapter>
<App />
</NuqsAdapter>Install the following optional components:
pnpm dlx shadcn@latest add "@diceui/data-table-sort-menu"pnpm dlx shadcn@latest add "@diceui/data-table-filter-menu"pnpm dlx shadcn@latest add "@diceui/data-table-command-filter-menu"pnpm dlx shadcn@latest add "@diceui/data-table-select-column"pnpm dlx shadcn@latest add "@diceui/data-table-actions-column"Layout
Import the components and compose them together:
import { DataTable } from "@/components/data-table/data-table";
import { DataTableSortMenu } from "@/components/data-table/data-table-sort-menu";
import { DataTableToolbar } from "@/components/data-table/data-table-toolbar";
import { useDataTable } from "@/hooks/use-data-table";
const { table } = useDataTable({
data,
columns,
pageCount,
});
return (
<DataTable table={table}>
<DataTableToolbar table={table}>
<DataTableSortMenu table={table} />
</DataTableToolbar>
</DataTable>
);For the advanced toolbar with a filter menu, swap the toolbar:
import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar";
import { DataTableFilterMenu } from "@/components/data-table/data-table-filter-menu";
return (
<DataTable table={table}>
<DataTableAdvancedToolbar table={table}>
<DataTableFilterMenu table={table} />
<DataTableSortMenu table={table} />
</DataTableAdvancedToolbar>
</DataTable>
);Walkthrough
Define columns with appropriate metadata:
import { Text, CalendarIcon, DollarSign } from "lucide-react";
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header";
const columns = React.useMemo(() => [
{
// Provide an unique id for the column
// This id will be used as query key for the column filter
id: "title",
accessorKey: "title",
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Title" />
),
cell: ({ row }) => <div>{row.getValue("title")}</div>,
// Define the column meta options for sorting, filtering, and view options
meta: {
label: "Title",
placeholder: "Search titles...",
variant: "text",
icon: Text,
},
// By default, the column will not be filtered. Set to `true` to enable filtering.
enableColumnFilter: true,
},
], []);Initialize the table state using the useDataTable hook:
import { useDataTable } from "@/hooks/use-data-table";
function DataTableDemo() {
const { table } = useDataTable({
data,
columns,
// Pass the total number of pages for server-side data,
// or set `mode: "client"` to sort, filter, and paginate in the browser
pageCount,
initialState: {
sorting: [{ id: "createdAt", desc: true }],
pagination: { pageSize: 10 },
},
// Unique identifier for rows, can be used for unique row selection
getRowId: (row) => row.id,
});
return (
// ... render table
);
}Pass the table instance to the DataTable, and DataTableToolbar components:
import { DataTable } from "@/components/data-table/data-table";
import { DataTableToolbar } from "@/components/data-table/data-table-toolbar";
import { DataTableSortMenu } from "@/components/data-table/data-table-sort-menu";
function DataTableDemo() {
return (
<DataTable table={table}>
<DataTableToolbar table={table}>
<DataTableSortMenu table={table} />
</DataTableToolbar>
</DataTable>
);
}For advanced filtering, use the DataTableAdvancedToolbar component:
import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar";
import { DataTableFilterMenu } from "@/components/data-table/data-table-filter-menu";
import { DataTableSortMenu } from "@/components/data-table/data-table-sort-menu";
function DataTableDemo() {
return (
<DataTable table={table}>
<DataTableAdvancedToolbar table={table}>
<DataTableFilterMenu table={table} />
<DataTableSortMenu table={table} />
</DataTableAdvancedToolbar>
</DataTable>
);
}Alternatively, swap out DataTableFilterMenu with DataTableCommandFilterMenu for a command palette-style interface:
import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar";
import { DataTableFilterMenu } from "@/components/data-table/data-table-filter-menu";
import { DataTableCommandFilterMenu } from "@/components/data-table/data-table-command-filter-menu";
import { DataTableSortMenu } from "@/components/data-table/data-table-sort-menu";
function DataTableDemo() {
return (
<DataTable table={table}>
<DataTableAdvancedToolbar table={table}>
<DataTableFilterMenu table={table} />
<DataTableCommandFilterMenu table={table} />
<DataTableSortMenu table={table} />
</DataTableAdvancedToolbar>
</DataTable>
);
}Render an action bar on row selection:
import { ActionBar } from "@/components/ui/action-bar";
function TableActionBar({ table }: { table: Table<Data> }) {
const rows = table.getFilteredSelectedRowModel().rows;
const onOpenChange = React.useCallback((open: boolean) => {
if (!open) {
table.toggleAllRowsSelected(false);
}
},
[table],
);
return (
<ActionBar open={rows.length > 0} onOpenChange={onOpenChange}>
{/* Add your custom actions here */}
</ActionBar>
);
}
function DataTableDemo() {
return (
<DataTable
table={table}
actionBar={<TableActionBar table={table} />}
>
<DataTableToolbar table={table} />
</DataTable>
);
}Row Selection
Use getDataTableSelectColumn to add a checkbox column for selecting rows:
import { getDataTableSelectColumn } from "@/components/data-table/data-table-select-column";
const columns = React.useMemo<ColumnDef<DataTableFeatures, Project>[]>(
() => [
getDataTableSelectColumn(),
// ...other columns
],
[],
);Cells are memoized and only re-render when the state they subscribe to changes. If you render your own selection-dependent cell, read the selection inside Subscribe instead of calling row.getIsSelected() directly:
import { Subscribe } from "@tanstack/react-table";
cell: ({ row }) => (
<Subscribe
source={row.table.atoms.rowSelection}
selector={(selection) => selection[row.id] === true}
>
{(isSelected) => (
<Checkbox
checked={isSelected}
onCheckedChange={(value) => row.toggleSelected(!!value)}
/>
)}
</Subscribe>
),Row Actions
Use getDataTableActionsColumn to add a menu of actions to each row:
import { getDataTableActionsColumn } from "@/components/data-table/data-table-actions-column";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
const columns = React.useMemo<ColumnDef<DataTableFeatures, Project>[]>(
() => [
// ...other columns
getDataTableActionsColumn({
actions: ({ row }) => (
<DropdownMenuItem onClick={() => onRowDelete(row.original)}>
Delete
</DropdownMenuItem>
),
}),
],
[onRowDelete],
);Pin it to the end with initialState.columnPinning.end, like { start: ["select"], end: ["actions"] }.
Client and Server Mode
useDataTable runs in one of two modes. Both keep pagination, sorting, and filters in the URL.
| Title | Description |
|---|---|
server | Default. Pass the current page of `data` and the total `pageCount` (required). Read the URL state on the server and query your database. |
client | Pass all rows as `data` and omit `pageCount`. The hook sorts, filters, and paginates them in the browser and derives the page count. |
pageCount is required in server mode and not allowed in client mode. TypeScript reports an error if you pass it in client mode or leave it out in server mode.
Client Mode
Use client mode when the full dataset fits in the browser:
const { table } = useDataTable({
mode: "client",
data,
columns,
getRowId: (row) => row.id,
});Server Mode
Use server mode for large datasets. Describe the filterable and sortable columns once, and share that config between the server and the column definitions.
Define the column configs and search params:
import { createSearchParamsCache } from "nuqs/server";
import type { DataTableColumnConfig } from "@/lib/data-table-types";
import { getDataTableSearchParams } from "@/lib/data-table-parsers";
export const tasksColumnConfigs = {
// Sortable, not filterable
code: { isSortable: false },
title: { variant: "text" },
status: { variant: "multiSelect" },
estimatedHours: { variant: "range" },
createdAt: { variant: "dateRange" },
} as const satisfies Record<string, DataTableColumnConfig>;
export const searchParamsCache = createSearchParamsCache(
getDataTableSearchParams({
columnConfigs: tasksColumnConfigs,
defaultSorting: [{ id: "createdAt", desc: true }],
}),
);Parse the search params on the server and turn them into a query:
import { getDataTableQuery } from "@/lib/data-table-parsers";
export default async function Page(props: {
searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
const search = searchParamsCache.parse(await props.searchParams);
// { page, perPage, sorting, filters, joinOperator }
const query = getDataTableQuery(search, tasksColumnConfigs);
const { data, pageCount } = await getTasks(query);
return <TasksTable data={data} pageCount={pageCount} />;
}Type the query function's input with DataTableColumnConfigsQuery<typeof tasksColumnConfigs>. Each filter has an id, variant, operator, and value to map onto your database query.
Spread getColumnOptions into each column so filtering and sorting match the config:
import { getColumnOptions } from "@/lib/data-table-parsers";
{
id: "title",
accessorKey: "title",
meta: { label: "Title", variant: "text" },
...getColumnOptions(tasksColumnConfigs.title),
}Pass the server data and pageCount to useDataTable. Pass startTransition from React.useTransition() to show loading states while the server re-renders:
const [isPending, startTransition] = React.useTransition();
const { table } = useDataTable({
data,
columns,
pageCount,
startTransition,
});URL Format
Each filterable column has its own search param, keyed by column id. The value is the operator followed by the filter value. When a filter uses the column's default operator, the operator is left out.
?title=report # text, default operator (contains)
?title=eq.Weekly%20report # text, "is"
?status=todo,done # multi-select, default operator (has any of)
?estimatedHours=2,8 # range, default operator (between)
?estimatedHours=gt.4 # range, "is greater than"
?createdAt=is.empty # no value
?sort=createdAt.desc,title.asc
?joinOperator=orAPI Reference
Column Definitions
The column definitions are used to define the columns of the data table.
const columns = React.useMemo<ColumnDef<Project>[]>(() => [
{
// Required: Unique identifier for the column
id: "title",
// Required: Key to access the data, `accessorFn` can also be used
accessorKey: "title",
// Optional: Custom header component
header: ({ column }) => (
<DataTableColumnHeader column={column} label="Title" />
),
// Optional: Custom cell component
cell: ({ row }) => <div>{row.getValue("title")}</div>,
// Optional: Meta options for filtering, sorting, and view options
meta: {
label: "Title",
placeholder: "Search titles...",
variant: "text",
icon: Text,
},
// By default, the column will not be filtered. Set to `true` to enable filtering.
enableColumnFilter: true,
},
{
id: "status",
// Access nested data using `accessorFn`
accessorFn: (row) => row.lineItem.status,
header: "Status",
meta: {
label: "Status",
variant: "select",
options: [
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
],
},
enableColumnFilter: true,
},
], []);Properties
Core configuration options for defining columns.
| Prop | Description |
|---|---|
id | Required: Unique identifier for the column |
accessorKey | Required: Key to access the data from the row |
accessorFn | Optional: Custom accessor function to access data |
header | Optional: Custom header component with column props |
cell | Optional: Custom cell component with row props |
meta | Optional: Meta options for accessing column metadata |
enableColumnFilter | By default, the column will not be filtered. Set to `true` to enable filtering |
enableSorting | Enable sorting for this column |
enableHiding | Enable column visibility toggle |
Column Meta
Column meta options for filtering, sorting, and view options.
| Prop | Description |
|---|---|
label | The display name for the column |
placeholder | The placeholder text for filter inputs |
variant | The type of filter to use (`text`, `number`, `select`, etc.) |
options | For select/multi-select filters, an array of options with `label`, `value`, and optional `count` and `icon` |
range | For range filters, a tuple of `[min, max]` values |
unit | For numeric filters, the unit to display (e.g., 'hr', '$') |
icon | The react component to use as an icon for the column |
Filter Variants
Available filter variants for column meta.
| Title | Description |
|---|---|
text | Text search with contains, equals, etc. |
number | Numeric filters with equals, greater than, less than, etc. |
range | Range filters with minimum and maximum values |
date | Date filters with equals, before, after, etc. |
dateRange | Date range filters with start and end dates |
boolean | Boolean filters with true/false values |
select | Single-select filters with predefined options |
multiSelect | Multi-select filters with predefined options |
Reference the TanStack Table Column Definitions Guide for detailed column definition guide.
useDataTable
A hook for initializing the data table with state management.
Prop
Type
DataTable
The main data table component.
Prop
Type
getDataTableActionsColumn
A utility function that returns an actions column with a dropdown menu for each row.
Prop
Type
DataTableColumnHeader
Custom header component for columns with sorting.
Prop
Type
DataTableToolbar
Standard toolbar with filtering and view options.
Prop
Type
DataTableAdvancedToolbar
Advanced toolbar with more comprehensive filtering capabilities.
Prop
Type
DataTableViewOptions
Controls column visibility and display preferences in the data table.
Prop
Type
DataTableSortMenu
List of applied sorting with ability to add, remove, and modify sorting.
Prop
Type
DataTableFilterMenu
List of applied filters with ability to add, remove, and modify filters.
Prop
Type
DataTableCommandFilterMenu
Command palette-style filter menu with ability to add, remove, and modify filters.
Prop
Type
DataTablePagination
Pagination controls for the data table.
Prop
Type
Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| Ctrl + Shift + FCmd + Shift + F | Toggles the filter menu. |
| Ctrl + Shift + SCmd + Shift + S | Toggles the sort menu. |
| BackspaceDelete | Removes the focused filter/sort item. Removes the last applied filter/sort when menu trigger is focused. |
Features
- Advanced Filtering - Multiple filter types (text, number, date, select, multi-select) with customizable operators
- URL State Management - Sync table state with URL search params using nuqs
- Sorting - Multi-column sorting with persistent state
- Pagination - Server-side or client-side pagination with customizable page sizes
- Column Visibility - Toggle column visibility with view options menu
- Row Selection - Single or multi-row selection with action bar
- Column Pinning - Pin columns to left or right for better UX
- Keyboard Navigation - Full keyboard support with shortcuts for filter and sort menus
- Responsive - Mobile-first design with overflow handling
- Customizable - Flexible API for custom filters, columns, and actions
Credits
- shadcn/ui - For the initial implementation of the data table.