<DataTable />
When you're building admin dashboards, you'll frequently need to display lists of data - whether it's posts, users, products, or any other resource. The DataTable
component handles all the common table functionality you need: sorting columns, filtering data, pagination, and action buttons.
Built on TanStack Table, it integrates directly with Refine's data fetching hooks, so you get features like server-side sorting and filtering without extra configuration.
Installation
Add the DataTable components to your project:
npx shadcn@latest add https://ui.refine.dev/r/data-table.json
This installs the main DataTable
component plus all the filtering, sorting, and pagination components you'll need. The CLI will automatically install the required dependencies like @tanstack/react-table
and react-day-picker
.
Basic Usage
Create a list view with a data table:
import { useMemo } from "react";
import { useTable } from "@refinedev/react-table";
import type { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@/components/refine-ui/data-table/data-table";
import {
ListView,
ListViewHeader,
} from "@/components/refine-ui/views/list-view";
type Post = {
id: number;
title: string;
status: string;
};
export default function PostList() {
const columns = useMemo<ColumnDef<Post>[]>(
() => [
{
// Column for ID field
id: "id",
accessorKey: "id", // Maps to the 'id' field in your data
header: "ID",
},
{
// Column for title field
id: "title",
accessorKey: "title", // Maps to the 'title' field in your data
header: "Title",
},
{
// Column for status field
id: "status",
accessorKey: "status", // Maps to the 'status' field in your data
header: "Status",
},
],
[],
);
const table = useTable<Post>({
columns,
refineCoreProps: {
resource: "posts", // Your API resource name
},
});
return (
<ListView>
<ListViewHeader title="Posts" />
<DataTable table={table} />
</ListView>
);
}
Advanced Example
Here's a comprehensive example showing how to create a data table with filtering, sorting, and pagination:
import { useMemo } from "react";
import { useTable } from "@refinedev/react-table";
import type { ColumnDef } from "@tanstack/react-table";
import { useList } from "@refinedev/core";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { MoreHorizontal } from "lucide-react";
import { DataTable } from "@/components/refine-ui/data-table/data-table";
import { DataTableSorter } from "@/components/refine-ui/data-table/data-table-sorter";
import {
DataTableFilterDropdownText,
DataTableFilterCombobox,
DataTableFilterDropdownNumeric,
} from "@/components/refine-ui/data-table/data-table-filter";
import {
EditButton,
DeleteButton,
ShowButton,
} from "@/components/refine-ui/buttons";
import {
ListView,
ListViewHeader,
} from "@/components/refine-ui/views/list-view";
type Post = {
id: number;
title: string;
content: string;
status: "published" | "draft" | "rejected";
category: { id: number };
createdAt: string;
};
export default function PostList() {
// Fetch categories for the dropdown filter
const { data: categories } = useList({
resource: "categories",
});
const columns = useMemo<ColumnDef<Post>[]>(
() => [
{
// ID column with sorting capability
id: "id",
accessorKey: "id",
size: 80,
header: ({ column }) => (
<DataTableSorter column={column}>ID</DataTableSorter>
),
cell: ({ getValue }) => getValue(),
},
{
// Title column with sorting and text filtering
id: "title",
accessorKey: "title",
size: 300,
header: ({ column }) => (
<div className="flex items-center gap-1">
<DataTableSorter column={column}>Title</DataTableSorter>
<DataTableFilterDropdownText column={column} />
</div>
),
},
{
// Status column with dropdown filter for specific values
id: "status",
accessorKey: "status",
size: 120,
header: ({ column }) => (
<div className="flex items-center gap-1">
<span>Status</span>
<DataTableFilterCombobox
column={column}
options={[
{ label: "Published", value: "published" },
{ label: "Draft", value: "draft" },
{ label: "Rejected", value: "rejected" },
]}
/>
</div>
),
},
{
// Actions column with edit/delete/show buttons
id: "actions",
size: 100,
enableSorting: false, // Disable sorting for action buttons
enableColumnFilter: false, // Disable filtering for action buttons
header: "Actions",
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<ShowButton hideText size="sm" recordItemId={row.original.id} />
<EditButton hideText size="sm" recordItemId={row.original.id} />
<DeleteButton hideText size="sm" recordItemId={row.original.id} />
</DropdownMenuContent>
</DropdownMenu>
),
},
],
[categories], // Re-create columns when categories data changes
);
const table = useTable<Post>({
columns,
refineCoreProps: {
resource: "posts",
},
});
return (
<ListView>
<ListViewHeader title="Posts" />
<DataTable table={table} />
</ListView>
);
}
This example demonstrates the key features: sortable columns, multiple filter types (text, numeric, dropdown), and action buttons in a dropdown menu.
Key Features
Column Sorting
Click any column header to sort by that field. The DataTable automatically handles the sorting state and sends the appropriate parameters to your data provider.
Filtering Options
The DataTable supports several filter types:
- Text filters: Search within text columns
- Numeric filters: Range filtering for numbers
- Dropdown filters: Select from predefined options
- Date filters: Date range selection
Action Buttons
Add action buttons for each row using the dropdown menu pattern shown above.
Component Structure
The DataTable is built from several focused components:
- DataTable: Main table component that handles rendering
- DataTableSorter: Column header sorting controls
- DataTableFilter: Various filter components for different data types
- DataTablePagination: Pagination controls
Handling Relational Data
When your main data records (e.g., posts
) contain references to other resources (e.g., a category
with an id
), you often need to fetch and display details from these related resources.
The @refinedev/core
package provides the useMany
hook, which is ideal for this scenario. You can collect all the foreign key IDs (e.g., category.id
from all posts in the current table view) and pass them to useMany
to fetch all related categories in a single request.
The recommended way to make this related data available to your column cell renderers is by using the setOptions
function returned by useTable
(from @refinedev/react-table
) to add the fetched data to the table's meta
object.
Adding Filters and Sorting
You can add different types of filters to your columns. Here are the most common ones:
Text Search Filter
import { DataTableFilterDropdownText } from "@/components/refine-ui/data-table/data-table-filter";
// In your column definition:
header: ({ column }) => (
<div className="flex items-center gap-1">
<DataTableSorter column={column}>Title</DataTableSorter>
<DataTableFilterDropdownText column={column} />
</div>
),
Dropdown/Select Filter
import { DataTableFilterCombobox } from "@/components/refine-ui/data-table/data-table-filter";
// In your column definition:
header: ({ column }) => (
<div className="flex items-center gap-1">
<span>Status</span>
<DataTableFilterCombobox
column={column}
options={[
{ label: "Published", value: "published" },
{ label: "Draft", value: "draft" },
]}
/>
</div>
),
Numeric Range Filter
import { DataTableFilterDropdownNumeric } from "@/components/refine-ui/data-table/data-table-filter";
// In your column definition:
header: ({ column }) => (
<div className="flex items-center gap-1">
<DataTableSorter column={column}>Price</DataTableSorter>
<DataTableFilterDropdownNumeric column={column} />
</div>
),
Date Range Filter
import { DataTableFilterDropdownDateRangePicker } from "@/components/refine-ui/data-table/data-table-filter";
// In your column definition:
header: ({ column }) => (
<div className="flex items-center gap-1">
<span>Created</span>
<DataTableFilterDropdownDateRangePicker
column={column}
formatDateRange={(dateRange) => {
if (!dateRange?.from || !dateRange?.to) return undefined;
return [
dateRange.from.toISOString(),
dateRange.to.toISOString(),
];
}}
/>
</div>
),
Column Sorting
import { DataTableSorter } from "@/components/refine-ui/data-table/data-table-sorter";
// In your column definition:
header: ({ column }) => (
<DataTableSorter column={column}>Column Name</DataTableSorter>
),
Props
DataTable
Prop | Type | Description |
---|---|---|
table | Table<TData> | TanStack Table instance from useTable hook |
DataTableSorter
Prop | Type | Description |
---|---|---|
column | Column<TData> | TanStack Table column instance |
children | React.ReactNode | Content to display (usually column name) |
DataTableFilterDropdownText
Prop | Type | Description |
---|---|---|
column | Column<TData> | TanStack Table column instance |
DataTableFilterCombobox
Prop | Type | Description |
---|---|---|
column | Column<TData> | TanStack Table column instance |
options | Array<{ label: string; value: string }> | Dropdown options |
multiple | boolean | Allow multiple selections |
DataTableFilterDropdownNumeric
Prop | Type | Description |
---|---|---|
column | Column<TData> | TanStack Table column instance |
- Installation
- Basic Usage
- Advanced Example
- Key Features
- Column Sorting
- Filtering Options
- Action Buttons
- Component Structure
- Handling Relational Data
- Adding Filters and Sorting
- Text Search Filter
- Dropdown/Select Filter
- Numeric Range Filter
- Date Range Filter
- Column Sorting
- Props
- DataTable
- DataTableSorter
- DataTableFilterDropdownText
- DataTableFilterCombobox
- DataTableFilterDropdownNumeric