Skip to content

Basic Table

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

A pragmatic starting point: pagination via DataTablePagination, per-column sort via DataTableColumnSort, and show/hide columns via DataTableViewMenu. Most app tables ship with just these three add-ons and never need anything else.

Open in
Product
Category
Price
Stock
Brand
Rating
Revenue
Release Date
LaptopElectronics
$999.00
50
Apple
1
$49,950
1/1/2024
MouseElectronics
$29.00
150
Samsung
3
$4,350
4/8/2024
KeyboardElectronics
$79.00
80
Nike
5
$6,320
7/15/2024
MonitorElectronics
$299.00
40
Adidas
2
$11,960
10/22/2024
HeadphonesElectronics
$149.00
70
Sony
4
$10,430
1/1/2024
TabletElectronics
$599.00
30
LG
1
$17,970
4/8/2024
SmartphoneElectronics
$799.00
25
Dell
3
$19,975
7/15/2024
WebcamElectronics
$89.00
60
HP
5
$5,340
10/22/2024
SpeakerElectronics
$199.00
45
Apple
2
$8,955
1/1/2024
PrinterElectronics
$159.00
20
Samsung
4
$3,180
4/8/2024
Preview with Controlled State
Open in
Product
Category
Price
Stock
Brand
Rating
Revenue
Release Date
LaptopElectronics
$999.00
50
Apple
1
$49,950
1/1/2024
MouseElectronics
$29.00
150
Samsung
3
$4,350
4/8/2024
KeyboardElectronics
$79.00
80
Nike
5
$6,320
7/15/2024
MonitorElectronics
$299.00
40
Adidas
2
$11,960
10/22/2024
HeadphonesElectronics
$149.00
70
Sony
4
$10,430
1/1/2024
Basic Table State
Live view of the basic table state with product inventory data
Total Products:25
Categories:2
Total Inventory Value:$227,512
Average Price:$282.20
Total Stock:1,188
Low Stock Items:8 (<30)
High Value Items:6 (>$500)
Sorting:None
Page:1 (Size: 5)
Hidden Columns:0
View Full State Object
Sorting:
[]
Pagination:
{
  "pageIndex": 0,
  "pageSize": 5
}
Column Visibility:
{}

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.

Install the DataTable core and add-ons for this example:

pnpm dlx shadcn@latest add @niko-table/data-table @niko-table/data-table-pagination @niko-table/data-table-view-menu @niko-table/data-table-column-sort

First time using @niko-table? See the Installation Guide to set up the registry.

For other add-ons or manual copy-paste, see the Installation Guide.

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 } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-title"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
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 } from "@/components/niko-table/core/data-table-root"
import { DataTable } from "@/components/niko-table/core/data-table"
import {
DataTableHeader,
DataTableBody,
} from "@/components/niko-table/core/data-table-structure"
import { DataTableToolbarSection } from "@/components/niko-table/components/data-table-toolbar-section"
import { DataTablePagination } from "@/components/niko-table/components/data-table-pagination"
import { DataTableViewMenu } from "@/components/niko-table/components/data-table-view-menu"
import { DataTableColumnHeader } from "@/components/niko-table/components/data-table-column-header"
import { DataTableColumnTitle } from "@/components/niko-table/components/data-table-column-title"
import { DataTableColumnSortMenu } from "@/components/niko-table/components/data-table-column-sort"
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: