Skip to content
Dice UI

Data Table

DocsAPI

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:

DataTableSortMenu:

pnpm dlx shadcn@latest add "@diceui/data-table-sort-menu"

DataTableFilterMenu:

pnpm dlx shadcn@latest add "@diceui/data-table-filter-menu"

DataTableCommandFilterMenu:

pnpm dlx shadcn@latest add "@diceui/data-table-command-filter-menu"

getDataTableSelectColumn:

pnpm dlx shadcn@latest add "@diceui/data-table-select-column"

getDataTableActionsColumn:

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(() => [
  {
    // The column id defaults to the `accessorKey` and is the URL key of its filter
    accessorKey: "title", 
    header: ({ column }) => <DataTableColumnHeader column={column} />,
    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, 
    // Defaults used when the URL has no sorting or page size
    config: { 
      defaultSorting: [{ id: "createdAt", desc: true }], 
      defaultPageSize: 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>
),

Press Escape inside the table to clear the row selection. Call event.preventDefault() in onEscapeKeyDown to keep it.

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"] }.

Set stickyHeader to scroll the rows inside the table and keep the header in view. Set the height with className:

<DataTable table={table} stickyHeader className="max-h-[600px]">
  <DataTableToolbar table={table} />
</DataTable>

Client and Server Mode

useDataTable runs in one of two modes. Both keep pagination, sorting, and filters in the URL.

TitleDescription
serverDefault. Pass the current page of `data` and the total `pageCount` (required). Read the URL state on the server and query your database.
clientPass 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 table once with createDataTableConfig, and share that config between the server and useDataTable.

Create the table config in a file both the server and the client can import:

lib/validations.ts
import type { DataTableColumnConfigs } from "@/lib/data-table-types";
import { createDataTableConfig } from "@/lib/data-table-config";
 
export const tasksTableConfig = createDataTableConfig({
  columns: {
    // Sortable, not filterable
    code: {},
    // Filterable, not sortable
    title: { variant: "text", enableSorting: false },
    status: { variant: "select" },
    estimatedHours: { variant: "number" },
    createdAt: { variant: "date" },
  } satisfies DataTableColumnConfigs<Task>,
  defaultSorting: [{ id: "createdAt", desc: true }],
});

The config's columns is an allowlist: a column it doesn't list can't be filtered or sorted, even from the URL. variant turns on filtering for the column, and enableSorting: false turns off sorting. DataTableColumnConfigs<Task> checks the column ids against your row type.

Parse the search params on the server and turn them into a query:

app/page.tsx
export default async function Page(props: {
  searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
  // { page, perPage, sorting, filters, joinOperator, globalFilter }
  const query = tasksTableConfig.parse(await props.searchParams);
  const { data, pageCount } = await getTasks(query);
 
  return <TasksTable data={data} pageCount={pageCount} />;
}

Type the query function's input with InferDataTableQuery<typeof tasksTableConfig> from @/lib/data-table-config. Each filter has an id, variant, operator, and value to map onto your database query.

Pass the config, the server data, and pageCount to useDataTable. The config sets each column's filter and sorting, so the column definitions don't need enableColumnFilter or meta.variant. Pass startTransition from React.useTransition() and set shallow to false to show loading states while the server re-renders:

import { tasksTableConfig } from "@/lib/validations";
 
const [isPending, startTransition] = React.useTransition();
 
const { table } = useDataTable({
  config: tasksTableConfig, 
  data,
  columns,
  pageCount,
  shallow: false,
  startTransition,
});

Global Filter

The global filter is stored in the URL as ?search= and comes back from config.parse as globalFilter. Pass globalFilterPlaceholder to DataTableToolbar or DataTableAdvancedToolbar to render a search input for it:

<DataTableToolbar table={table} globalFilterPlaceholder="Search tasks..." />

In client mode, TanStack Table's global filter runs over the rows. Set enableGlobalFilter: false in the config to turn it off.

Multiple Tables

Give each config an id to render several tables on one page. The id prefixes every URL key, so the tables don't share state:

export const tasksTableConfig = createDataTableConfig({
  id: "tasks",
  columns,
});
// ?tasks.page=2&tasks.status=todo

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                 # select, default operator (has any of)
?estimatedHours=2,8               # number, two values (between)
?estimatedHours=gt.4              # number, "is greater than"
?createdAt=is.empty               # no value
?sort=createdAt.desc,title.asc
?joinOperator=or
?search=report                    # global filter

API Reference

Column Definitions

The column definitions are used to define the columns of the data table.

const columns = React.useMemo<ColumnDef<Project>[]>(() => [
  {
    // Required: Key to access the data, also used as the column id
    accessorKey: "title", 
    // Optional: Custom header component
    header: ({ column }) => <DataTableColumnHeader column={column} />,
    // 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, 
  },
  {
    // Required with `accessorFn`: Unique identifier for the column
    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.

PropDescription
idUnique identifier for the column, used as its URL key. Defaults to `accessorKey`; required with `accessorFn`
accessorKeyRequired: Key to access the data from the row
accessorFnOptional: Custom accessor function to access data
headerOptional: Custom header component with column props
cellOptional: Custom cell component with row props
metaOptional: Meta options for accessing column metadata
enableColumnFilterBy default, the column will not be filtered. Set to `true` to enable filtering. A config from `createDataTableConfig` sets this from its `variant`
enableSortingEnable sorting for this column. A config from `createDataTableConfig` sets this from its `enableSorting`
enableHidingEnable column visibility toggle

Column Meta

Column meta options for filtering, sorting, and view options.

PropDescription
labelThe display name for the column
placeholderThe placeholder text for filter inputs
variantThe column's data type, which picks its filter (`text`, `number`, `date`, `boolean`, `select`). A config from `createDataTableConfig` sets this
optionsFor `select` filters, an array of options with `label`, `value`, and optional `count` and `icon`
multipleFor `select` filters, whether the toolbar filter can pick more than one option. Defaults to `true`
minFor `number` filters, the lower bound of the toolbar slider
maxFor `number` filters, the upper bound of the toolbar slider
unitFor numeric filters, the unit to display (e.g., 'hr', '$')
iconThe react component to use as an icon for the column

Filter Variants

Available variants for the table config and column meta.

TitleDescription
textText search with contains, equals, etc.
numberNumeric filters with equals, greater than, less than, between, etc. The toolbar shows a slider when the column sets `min` and `max`, or in client mode.
dateDate filters with equals, before, after, between, etc. The toolbar picks a start and end date.
booleanBoolean filters with true/false values
selectFilters with predefined options. Set `multiple: false` in the column meta to pick one option in the toolbar.

Reference the TanStack Table Column Definitions Guide for detailed column definition guide.

createDataTableConfig

Creates the table config shared by the server and useDataTable. Read the URL on the server with config.parse(searchParams).

Prop

Type

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

KeyDescription
Ctrl + Shift + FCmd + Shift + FToggles the filter menu.
Ctrl + Shift + SCmd + Shift + SToggles the sort menu.
BackspaceDeleteRemoves the focused filter/sort item. Removes the last applied filter/sort when menu trigger is focused.
EscapeClears the row selection when focus is inside the table.

Features

  • Advanced Filtering - Multiple filter types (text, number, date, boolean, select) with customizable operators
  • Shared Config - One config per table for the server and the client, with an optional id for multiple tables on a page
  • URL State Management - Sync pagination, sorting, filters, and the global filter with URL search params using nuqs
  • Sorting - Multi-column sorting with persistent state
  • Sticky Header - Keep the header in view while the rows scroll
  • 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.

On this page