Skip to content

Column Pinning Table

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

Pin one or more columns to the left or right edge so they stay anchored during horizontal scroll — handy for identifier columns (Order ID, Name) on the left and action columns (menu, edit) on the right. Sticky positioning and z-index layering are handled by the core; you just declare which columns pin and where.

Open in
Order ID
Customer
Product
Amount
Status
Date
Region
Actions
ORD-001John DoePremium Widget$299.99delivered1/15/2024North America
ORD-002Jane SmithBasic Kit$149.50shipped1/18/2024Europe
ORD-003Bob JohnsonPro Bundle$599.00pending1/20/2024Asia Pacific
ORD-004Alice WilliamsStarter Pack$79.99delivered1/22/2024North America
ORD-005Charlie BrownEnterprise Suite$1,299.00shipped1/25/2024Europe
ORD-006Diana PrincePremium Widget$299.99cancelled1/28/2024Asia Pacific
ORD-007Ethan HuntBasic Kit$149.50pending2/1/2024North America
ORD-008Fiona GreenPro Bundle$599.00delivered2/5/2024Europe
ORD-009George MillerStarter Pack$79.99shipped2/8/2024Asia Pacific
ORD-010Hannah LeeEnterprise Suite$1,299.00delivered2/12/2024North America
Preview with Controlled State
Open in
Order ID
Customer
Product
Amount
Status
Date
Region
Actions
ORD-001John DoePremium Widget$299.99delivered1/15/2024North America
ORD-002Jane SmithBasic Kit$149.50shipped1/18/2024Europe
ORD-003Bob JohnsonPro Bundle$599.00pending1/20/2024Asia Pacific
ORD-004Alice WilliamsStarter Pack$79.99delivered1/22/2024North America
ORD-005Charlie BrownEnterprise Suite$1,299.00shipped1/25/2024Europe
ORD-006Diana PrincePremium Widget$299.99cancelled1/28/2024Asia Pacific
ORD-007Ethan HuntBasic Kit$149.50pending2/1/2024North America
ORD-008Fiona GreenPro Bundle$599.00delivered2/5/2024Europe
ORD-009George MillerStarter Pack$79.99shipped2/8/2024Asia Pacific
ORD-010Hannah LeeEnterprise Suite$1,299.00delivered2/12/2024North America
Current Table State
Live view of the current table state for demonstration purposes
Search Query:None
Total Items:10
Sorting:None
Page:1 (Size: 10)
Hidden Columns:0
Pinned Columns:1 Left, 1 Right
View Full State Object
Sorting:
[]
Column Visibility:
{}
Column Pinning:
{
  "left": [
    "id"
  ],
  "right": [
    "actions"
  ]
}

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)