Skip to content

Column Pinning Table

Pin columns to the left or right edge for easy reference while scrolling.

Open in
Preview with Controlled State
Open in

Column pinning keeps important columns visible while users scroll horizontally. Pin identifier columns (like Order ID) to the left and action columns to the right.

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

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

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’ll build a table showing orders. Here’s our data:

type Order = {
id: string
customer: string
product: string
amount: number
status: "pending" | "shipped" | "delivered" | "cancelled"
date: string
region: string
}
const data: Order[] = [
{
id: "ORD-001",
customer: "John Doe",
product: "Premium Widget",
amount: 299.99,
status: "delivered",
date: "2024-01-15",
region: "North America",
},
// ...
]

Set initialState.columnPinning to pin columns on mount:

column-pinning.tsx
<DataTableRoot
data={data}
columns={columns}
initialState={{
columnPinning: {
left: ["id"], // Pin Order ID to left
right: ["actions"], // Pin actions to right
},
}}
>
<DataTable>
<DataTableHeader />
<DataTableBody />
</DataTable>
</DataTableRoot>

Set explicit size for each column to enable horizontal scrolling:

columns.tsx
const columns: DataTableColumnDef<Order>[] = [
{
accessorKey: "id",
size: 110,
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle title="Order ID" />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Order ID" },
},
{
accessorKey: "customer",
size: 160,
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle title="Customer" />
<DataTableColumnSortMenu />
</DataTableColumnHeader>
),
meta: { label: "Customer" },
},
// ... more columns
]

Add pinning options to column actions for user-controlled pinning:

{
accessorKey: "customer",
size: 160,
header: () => (
<DataTableColumnHeader>
<DataTableColumnTitle title="Customer" />
<DataTableColumnActions>
<DataTableColumnSortOptions />
<DataTableColumnPinOptions />
</DataTableColumnActions>
</DataTableColumnHeader>
),
}

Manage pinning state externally:

column-pinning-state.tsx
import { useState } from "react"
import { type ColumnPinningState } from "@tanstack/react-table"
export function ControlledPinningTable({ data }: { data: Order[] }) {
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>({
left: ["id"],
right: ["actions"],
})
return (
<DataTableRoot
data={data}
columns={columns}
state={{ columnPinning }}
onColumnPinningChange={setColumnPinning}
>
{/* ... */}
</DataTableRoot>
)
}

✅ Use Column Pinning when:

  • Tables have many columns requiring horizontal scroll
  • Key identifiers (ID, Name) must stay visible
  • Actions column should be always accessible

❌ Consider other options when:

  • All columns fit on screen (no scrolling needed)
  • Mobile-first design (pinning adds complexity)