Skip to main content

Column pinning

Pinned columns stick to the left or right edge while the rest of the grid scrolls horizontally. Turn it on with enableColumnPinning.

Loading demo…

Open the Columns panel in the toolbar (the ⊞ icon) — each column has pin-left / pin-right toggles next to its show/hide (eye) toggle. Click an active side again to unpin.

Enable it

<DataTable
columns={columns}
data={rows}
enableColumnPinning
enableColumnVisibility // surfaces the per-column pin toggles in the Columns panel
/>

enableColumnVisibility isn't required, but it's what renders the toolbar's pin UI. Without it you can still pin via initialState or the apiRef.

Default pinned columns

When enableColumnPinning is on, the built-in selection (_selection) and expander (_expanding) columns are pinned left automatically, so the checkbox and expand toggle stay visible while scrolling. This merges with your own initialState.columnPinning — you only pin your columns, the special ones stay left:

<DataTable
columns={columns}
data={rows}
enableColumnPinning
enableRowSelection
initialState={{
columnPinning: {
left: ['name'], // name stuck left (checkbox/expander auto-pinned before it)
right: ['actions'], // actions stuck right
},
}}
/>

To override the default, pin a special column yourself by its id — _selection (checkbox) or _expanding (expander) — anywhere in left / right, and the automatic pin steps aside.

Pinned columns always render in column order. Within each side they follow the columns' order (or your drag-reorder), so a column that's last overall stays rightmost when pinned right, regardless of the order you pinned things in.

Imperative control

Drive pinning from your own UI through apiRef:

apiRef.current?.columnPinning.pinColumnLeft('name');
apiRef.current?.columnPinning.pinColumnRight('actions');
apiRef.current?.columnPinning.unpinColumn('name');
apiRef.current?.columnPinning.resetColumnPinning(); // back to the initial/default pinning

Listen for changes with the onColumnPinningChange prop.

Props

PropTypeDescription
enableColumnPinningbooleanEnable pinning + the toolbar pin toggles
initialState.columnPinning{ left?: string[]; right?: string[] }Initial (and reset) pinned columns
onColumnPinningChange(state) => voidFires when pinning changes

The detail-panel row from row expansion always spans the full width and is not affected by pinning.

Row pinning

Pin individual rows to the top and/or bottom so they stay visible while the rest scroll — pinned rows survive sort, filter, and pagination. Turn it on with enableRowPinning. Below, Liam starts pinned to the top and Zoe to the bottom; use the pushpin buttons in each row's actions to pin/unpin.

Loading demo…

:::note Client data mode only Row pinning re-hydrates pinned rows by id from the full local dataset, so it requires client-side data (like footer aggregation). It's ignored under server pagination, where the engine holds only one page. Top-level rows only — tree sub-rows can't be pinned. Give rows a stable getRowId so pins track the right record. :::

Pin rows

There's no built-in pin button (the grid has no per-row gutter), so pin via the imperative apiRef or the exported createRowPinAction helper, which returns a row action you spread into getRowActions:

import { DataTable, createRowPinAction } from '@ackplus/mui-tanstack-data-grid';

const apiRef = useRef(null);

<DataTable
apiRef={apiRef}
columns={columns}
data={rows}
enableRowPinning
initialState={{ rowPinning: { top: ['1'], bottom: ['8'] } }} // pre-pin by row id
getRowActions={(row) => [
createRowPinAction(apiRef, row, 'top'), // label/icon flip to "Unpin" when pinned
createRowPinAction(apiRef, row, 'bottom'),
]}
/>;

Or drive it directly:

apiRef.current?.rowPinning.pinRowTop('1');
apiRef.current?.rowPinning.pinRowBottom('8');
apiRef.current?.rowPinning.unpinRow('1');
apiRef.current?.rowPinning.resetRowPinning(); // back to initialState.rowPinning
apiRef.current?.rowPinning.getPinnedRowIds(); // { top: [...], bottom: [...] }

Listen with onRowPinningChange. Pins are included in saveLayout()/restoreLayout(); to persist them across reloads add 'rowPinning' to persist.include and supply a stable getRowId.

Row-pinning props

PropTypeDescription
enableRowPinningbooleanEnable top/bottom row pinning (client data mode only)
initialState.rowPinning{ top?: string[]; bottom?: string[] }Initial (and reset) pinned row ids
onRowPinningChange(state) => voidFires when row pinning changes