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(() => [
  {
    // 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.

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 filterable and sortable columns once, and share that config between the server and the column definitions.

Define the column configs and search params:

lib/validations.ts
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:

app/page.tsx
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=or

API 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.

PropDescription
idRequired: Unique identifier for the column
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
enableSortingEnable sorting for this column
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 type of filter to use (`text`, `number`, `select`, etc.)
optionsFor select/multi-select filters, an array of options with `label`, `value`, and optional `count` and `icon`
rangeFor range filters, a tuple of `[min, max]` values
unitFor numeric filters, the unit to display (e.g., 'hr', '$')
iconThe react component to use as an icon for the column

Filter Variants

Available filter variants for column meta.

TitleDescription
textText search with contains, equals, etc.
numberNumeric filters with equals, greater than, less than, etc.
rangeRange filters with minimum and maximum values
dateDate filters with equals, before, after, etc.
dateRangeDate range filters with start and end dates
booleanBoolean filters with true/false values
selectSingle-select filters with predefined options
multiSelectMulti-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

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.

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.

On this page