Skip to content

Basic Table

Add pagination, sorting, and column visibility controls to your data table.

Open in
Preview with Controlled State
Open in

The Basic Table builds on the Simple Table by adding pagination, sorting, and column visibility. This is the most common table implementation and suitable for most use cases.

  1. Add the <Table /> component to your project:
npx shadcn@latest add table button dropdown-menu
  1. Add tanstack/react-table dependency:
npm install @tanstack/react-table
  1. Copy the DataTable components into your project. See the Installation Guide for detailed instructions.

We are going to build a table to show products. Here’s what our data looks like:

type Product = {
id: string
name: string
category: string
price: number
stock: number
}
const data: Product[] = [
{ id: "1", name: "Laptop", category: "Electronics", price: 999, stock: 50 },
{ id: "2", name: "Mouse", category: "Electronics", price: 29, stock: 150 },
// ...
]

Let’s start by building a basic table with pagination and sorting.

First, we’ll define our columns with sortable headers.

columns.tsx
"use client"
import {
DataTableColumnHeader,
DataTableColumnTitle,
DataTableColumnSortMenu,
} from "@/components/niko-table/components"
import type { DataTableColumnDef } from "@/components/niko-table/types"
export type Product = {
id: string
name: string
category: string
price: number
stock: number
}
export const columns: DataTableColumnDef<Product>[] = [
{
accessorKey: "name",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Product" },
},
{
accessorKey: "category",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Category" },
},
{
accessorKey: "price",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Price" },
cell: ({ row }) => {
const price = parseFloat(row.getValue("price"))
return <div className="font-medium">${price.toFixed(2)}</div>
},
},
{
accessorKey: "stock",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Stock" },
},
]

Next, we’ll create a <DataTable /> component with pagination and sorting enabled.

basic-table.tsx
"use client"
import {
DataTableRoot,
DataTable,
DataTableHeader,
DataTableBody,
} from "@/components/niko-table/core"
import {
DataTableToolbarSection,
DataTablePagination,
DataTableViewMenu,
DataTableColumnHeader,
DataTableColumnTitle,
DataTableColumnSortMenu,
} from "@/components/niko-table/components"
import type { DataTableColumnDef } from "@/components/niko-table/types"
type Product = {
id: string
name: string
category: string
price: number
stock: number
}
const columns: DataTableColumnDef<Product>[] = [
{
accessorKey: "name",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Product" },
},
{
accessorKey: "category",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Category" },
},
{
accessorKey: "price",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Price" },
cell: ({ row }) => {
const price = parseFloat(row.getValue("price"))
return <div className="font-medium">${price.toFixed(2)}</div>
},
},
{
accessorKey: "stock",
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Stock" },
},
]
export function BasicTable({ data }: { data: Product[] }) {
return (
<DataTableRoot
data={data}
columns={columns}
config={{
enablePagination: true,
enableSorting: true,
enableMultiSort: true,
initialPageSize: 10,
}}
>
<DataTableToolbarSection className="justify-between">
<h2 className="text-lg font-semibold">Products</h2>
<DataTableViewMenu />
</DataTableToolbarSection>
<DataTable>
<DataTableHeader />
<DataTableBody />
</DataTable>
<DataTablePagination pageSizeOptions={[5, 10, 20]} />
</DataTableRoot>
)
}

Pagination is automatically enabled when you set enablePagination: true in the config. The DataTablePagination component provides:

  • Page navigation (Previous/Next)
  • Page size selector
  • Current page info (e.g., “1-10 of 100”)

You can customize the available page sizes:

<DataTablePagination pageSizeOptions={[10, 25, 50, 100]} />

Sorting is enabled by using DataTableColumnHeader in your column definitions. Click any column header to sort by that column.

Hold Shift and click multiple column headers to sort by multiple columns:

<DataTableRoot
data={data}
columns={columns}
config={{
enableMultiSort: true, // Enable multi-column sorting
}}
>
{/* ... */}
</DataTableRoot>

To disable sorting for a specific column:

{
accessorKey: "id",
header: "ID",
enableSorting: false, // This column cannot be sorted
}

The DataTableViewMenu component adds a dropdown to show/hide columns:

<DataTableToolbarSection className="justify-between">
<h2 className="text-lg font-semibold">Products</h2>
<DataTableViewMenu />
</DataTableToolbarSection>

To prevent specific columns from being hidden:

{
accessorKey: "id",
header: "ID",
enableHiding: false, // This column cannot be hidden
}

Full control over table state:

import { useState } from "react"
import type {
PaginationState,
SortingState,
VisibilityState,
} from "@tanstack/react-table"
export function ControlledBasicTable({ data }: { data: Product[] }) {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
})
const [sorting, setSorting] = useState<SortingState>([])
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
return (
<DataTableRoot
data={data}
columns={columns}
state={{
pagination,
sorting,
columnVisibility,
}}
onPaginationChange={setPagination}
onSortingChange={setSorting}
onColumnVisibilityChange={setColumnVisibility}
>
<DataTableToolbarSection className="justify-between">
<h2 className="text-lg font-semibold">Products</h2>
<DataTableViewMenu />
</DataTableToolbarSection>
<DataTable>
<DataTableHeader />
<DataTableBody />
</DataTable>
<DataTablePagination />
</DataTableRoot>
)
}

✅ Use Basic Table when:

  • You have 50+ rows that need pagination
  • Users need to sort data
  • You want column visibility controls
  • You don’t need search or filtering

❌ Consider other options when: