Pin rows to the top or bottom of the grid.
Row pinning allows users to pin important rows to the top or bottom of the grid, keeping them visible during scrolling.
<ActiveGrid
enableRowPinning={true}
maxPinnedRows={10}
columns={columns}
data={data}
{...}
/>Enable row pinning:
<ActiveGrid
enableRowPinning={true}
columns={columns}
data={data}
{...}
/>booleanfalse<ActiveGrid
enableRowPinning={true}
{...}
/>number10<ActiveGrid
enableRowPinning={true}
maxPinnedRows={5}
{...}
/>Interface for initial pinned rows:
interface PinnedRowsBuffer<TData> {
top: TData[];
bottom: TData[];
}const initialPinnedRows: PinnedRowsBuffer<User> = {
top: [
{ id: '1', name: 'Admin User', role: 'admin' },
],
bottom: [
{ id: '999', name: 'Total', role: 'summary' },
],
};
<ActiveGrid
enableRowPinning={true}
pinnedRows={initialPinnedRows}
{...}
/>When row pinning is enabled, the row menu automatically shows:
<ActiveGrid
enableRowPinning={true}
rowMenu={{
features: {
pin: true, // Show pin options (default: true)
},
}}
{...}
/>function PinButton({ row }) {
const handlePin = () => {
row.pin('top');
};
return <Button onClick={handlePin}>Pin to Top</Button>;
}function PinButton({ row }) {
const handlePin = () => {
row.pin('bottom');
};
return <Button onClick={handlePin}>Pin to Bottom</Button>;
}function UnpinButton({ row }) {
const handleUnpin = () => {
row.pin(false);
};
return <Button onClick={handleUnpin}>Unpin</Button>;
}function TogglePinButton({ row }) {
const isPinned = row.getIsPinned();
const handleToggle = () => {
if (isPinned) {
row.pin(false);
} else {
row.pin('top');
}
};
return (
<Button onClick={handleToggle}>
{isPinned ? 'Unpin' : 'Pin'}
</Button>
);
}// Check if row is pinned
const isPinned = row.getIsPinned(); // 'top' | 'bottom' | false
// Check if pinned to top
const isPinnedTop = row.getIsPinned() === 'top';
// Check if pinned to bottom
const isPinnedBottom = row.getIsPinned() === 'bottom';Pinned rows automatically receive CSS classes:
Add visual indicators for pinned rows:
Common use case: pin totals or summaries:
Row pinning works with server mode, but pinned rows must be in the data:
When maxPinnedRows is reached, pinning additional rows will:
Rows are displayed in this order:
Pinned row state can be persisted:
Row pinning via row menu can be accessed with keyboard:
Pinned rows include:
import { RowPinningState } from '@tanstack/react-table';
function UsersTable() {
const [rowPinning, setRowPinning] = useState<RowPinningState>({
top: [],
bottom: [],
});
return (
<ActiveGrid
enableRowPinning={true}
state={{ rowPinning }}
onRowPinningChange={setRowPinning}
{...}
/>
);
}const [rowPinning, setRowPinning] = useState<RowPinningState>({
top: ['1', '2'], // Row IDs
bottom: ['999'],
});.grid-row--pinned-top {
/* Row pinned to top */
}
.grid-row--pinned-bottom {
/* Row pinned to bottom */
}<ActiveGrid
enableRowPinning={true}
settings={{
rowClassRules: {
'bg-blue-50 border-b-2 border-blue-500': (row) =>
row.getIsPinned() === 'top',
'bg-green-50 border-t-2 border-green-500': (row) =>
row.getIsPinned() === 'bottom',
},
}}
{...}
/>{
id: 'pin-indicator',
header: '',
size: 40,
cell: ({ row }) => {
const isPinned = row.getIsPinned();
if (!isPinned) return null;
return (
<Pin
className={cn(
"h-4 w-4",
isPinned === 'top' && "text-blue-500",
isPinned === 'bottom' && "text-green-500"
)}
/>
);
},
}const users = [
{ id: '1', name: 'John', revenue: 1000 },
{ id: '2', name: 'Jane', revenue: 2000 },
{ id: '3', name: 'Bob', revenue: 1500 },
];
const totalRow = {
id: 'total',
name: 'TOTAL',
revenue: users.reduce((sum, u) => sum + u.revenue, 0),
};
<ActiveGrid
enableRowPinning={true}
data={users}
pinnedRows={{
top: [],
bottom: [totalRow],
}}
settings={{
rowClassRules: {
'font-bold bg-muted': (row) => row.id === 'total',
},
}}
{...}
/><ActiveGrid
mode="server"
fetchFn={async (params) => {
const result = await api.getData(params);
return {
data: [
...result.pinnedTop,
...result.data,
...result.pinnedBottom,
],
totalCount: result.total,
};
}}
enableRowPinning={true}
{...}
/><ActiveGrid
enableRowPinning={true}
maxPinnedRows={3}
// User tries to pin 4th row → warning shown
{...}
/>import { useState } from 'react';
import { RowPinningState } from '@tanstack/react-table';
import { Pin, PinOff } from 'lucide-react';
type Order = {
id: string;
orderNumber: string;
customer: string;
amount: number;
status: string;
};
function OrdersTable() {
const [rowPinning, setRowPinning] = useState<RowPinningState>({
top: [],
bottom: [],
});
const columns: GridColumnDef<Order>[] = [
{
id: 'pin-status',
header: '',
size: 40,
cell: ({ row }) => {
const isPinned = row.getIsPinned();
if (!isPinned) return null;
return (
<Pin
className={cn(
"h-4 w-4",
isPinned === 'top' && "text-blue-500",
isPinned === 'bottom' && "text-green-500"
)}
/>
);
},
},
{
accessorKey: 'orderNumber',
header: 'Order #',
},
{
accessorKey: 'customer',
header: 'Customer',
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
valueFormatter: (value) => `$${value.toFixed(2)}`,
},
},
{
accessorKey: 'status',
header: 'Status',
},
{
id: 'actions',
header: 'Actions',
cell: ({ row }) => {
const isPinned = row.getIsPinned();
return (
<div className="flex gap-2">
{!isPinned && (
<>
<Button
size="sm"
variant="ghost"
onClick={() => row.pin('top')}
>
<Pin className="h-4 w-4 mr-1" />
Pin Top
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => row.pin('bottom')}
>
<Pin className="h-4 w-4 mr-1" />
Pin Bottom
</Button>
</>
)}
{isPinned && (
<Button
size="sm"
variant="ghost"
onClick={() => row.pin(false)}
>
<PinOff className="h-4 w-4 mr-1" />
Unpin
</Button>
)}
</div>
);
},
},
];
return (
<ActiveGrid
columns={columns}
data={orders}
enableRowPinning={true}
maxPinnedRows={5}
state={{ rowPinning }}
onRowPinningChange={setRowPinning}
rowMenu={{
features: {
pin: true,
},
}}
settings={{
rowClassRules: {
'bg-blue-50 border-b-2 border-blue-500': (row) =>
row.getIsPinned() === 'top',
'bg-green-50 border-t-2 border-green-500': (row) =>
row.getIsPinned() === 'bottom',
},
}}
/>
);
}<ActiveGrid
enableRowPinning={true}
enablePersistence={true}
gridId="orders"
// Pinned rows saved to localStorage
{...}
/>import { RowPinningState } from '@tanstack/react-table';
type User = {
id: string;
name: string;
};
const [rowPinning, setRowPinning] = useState<RowPinningState>({
top: [],
bottom: [],
});
<ActiveGrid<User>
enableRowPinning={true}
state={{ rowPinning }}
onRowPinningChange={setRowPinning}
{...}
/>