Column pinning
Set pinned: 'left' or pinned: 'right' on any column to keep it visible while the user scrolls horizontally through a wide table. Pinned columns useposition: sticky and render a crisp boundary line on the band edge to indicate the boundary between sticky and scrolling content.
Column pinning
Choose how many columns to pin on each side, or none to reset. Scroll the table horizontally and pinned columns stay in place. Resize any column and the pin offsets update automatically.
Pinning is cumulative from the edge: picking the third column pins the first three. Drag unpinned headers to reorder, resize any column from its right edge, and scroll horizontally to see pinned columns hold their position.
import DataTable, { type TableColumn } from 'react-data-table-component';
const columns: TableColumn<Row>[] = [
{ id: 'name', name: 'Name', selector: r => r.name, width: '180px', pinned: 'left' },
{ id: 'role', name: 'Role', selector: r => r.role, width: '220px' },
{ id: 'dept', name: 'Department', selector: r => r.department, width: '180px' },
{ id: 'loc', name: 'Location', selector: r => r.location, width: '200px' },
{ id: 'salary', name: 'Salary', selector: r => r.salary, width: '140px', right: true },
{ id: 'status', name: 'Status', selector: r => r.status, width: '120px', pinned: 'right' },
];
<DataTable columns={columns} data={data} resizable highlightOnHover />;Columns need widths
Pinning is only visible when the table overflows its container horizontally — a pinned column is position: sticky, and without horizontal scroll there is nothing to stick against. Columns without a width are fluid: they shrink and grow to fill the container, so the table never overflows and pinning appears to do nothing. Give your columns width (or minWidth) values whose total exceeds the container, as every example on this page does. Explicit widths also keep the sticky offsets exact when several columns are pinned on the same side, since offsets are computed from the declared widths.
How it works
The table measures the widths of all pinned columns and computes cumulativeleft or right CSS offsets so pinned columns stack correctly when more than one is pinned on the same side. Header and body cells both receiveposition: sticky with those offsets.
When responsive is enabled and at least one column is pinned, the table also renders a custom horizontal scrollbar below the body that excludes the pinned regions — so the scroll thumb only spans the scrollable middle section. The browser's native horizontal scrollbar is hidden in this mode.
The ordering rule
Pinned columns must form a contiguous band on each side. Internally the table enforces this via normalizePins:
- The first N columns become the left-pinned band, where N is the count of
pinned: 'left'columns. - The last M columns become the right-pinned band.
- Anything in the middle has
pinnedstripped.
This matters most when reorder: true is enabled. Dragging a column into the pinned band pins it; dragging it out unpins it. You don't need to think about pin state when reordering — the band membership follows position.
Pinning multiple columns
Pin as many columns as you like on each side. Offsets cascade automatically:
const columns: TableColumn<Row>[] = [
{ id: 'id', name: 'ID', width: '80px', pinned: 'left' },
{ id: 'name', name: 'Name', width: '180px', pinned: 'left' },
// ... scrolling columns ...
{ id: 'status', name: 'Status', width: '120px', pinned: 'right' },
{ id: 'action', name: '', width: '64px', pinned: 'right' },
];ID sticks at left: 0 and Name sticks atleft: 80px (the width of ID). On the right side it's mirrored:Action sticks at right: 0 and Status atright: 64px.
With selectable rows or expandable rows
The checkbox and expander columns are always pinned to the left edge — they don't needpinned set. When you also pin a regular column to the left, its offset accounts for the checkbox/expander width automatically.
<DataTable
columns={columns} // Name has pinned: 'left'
data={data}
selectableRows // checkbox column pins at 0
expandableRows // expander column pins after the checkbox
/>;
// → Name sticks at left: 96px (48px checkbox + 48px expander)The system column width defaults to 48px and is controlled by the--rdt-system-col-width CSS variable. Override it in your stylesheet if you customize the checkbox/expander cell width — pinning offsets will stay aligned because they read the same variable.
/* In your global CSS */
.rdt_table {
--rdt-system-col-width: 56px;
}With resizable columns
Pinned columns are fully compatible with resizable. Resizing a pinned column triggers a re-render with new offsets so columns pinned further from the edge shift to match.
<DataTable columns={columns} data={data} resizable />;With fixed header
Pinning and fixedHeader compose. Pinned header cells use z-index: 2versus z-index: 1 for body cells so the intersection (top-left / top-right corner) stays above scrolling body content.
<DataTable columns={columns} data={data} fixedHeader fixedHeaderScrollHeight="400px" />;Not supported with column groups
When you pass columnGroups, the header switches to a CSS grid layout that is incompatible with sticky positioning. The table will silently strip pinnedfrom your columns and log a one-time console.warn in development.
Pick one or the other for any given table. If you need both grouped headers and a frozen column, consider splitting the table or letting the first column be a wider summary cell.
Customizing the pin boundary
A crisp 1px line marks the inner edge of each pin band — classrdt_pinLeftLast on the rightmost left-pinned column andrdt_pinRightFirst on the leftmost right-pinned column. It defaults to the theme's divider color. Override it via the --rdt-color-pin-border CSS variable:
.rdt_table {
--rdt-color-pin-border: rgba(0, 0, 0, 0.3); /* darker boundary line */
}When not to pin
- Narrow viewports. Pinning two columns from each side on a 320px-wide phone leaves almost no scrollable space. Consider
hide: 'sm'on the same columns instead. - Variable-width content. Pin columns with predictable widths. A pinned column without a fixed
widthstill works but may shift content during initial render. - Single-screen tables. If your table fits on screen without horizontal scrolling, pinning adds visual noise (the boundary line) for no benefit.
Requirements
- Each pinned column must have an explicit
id— the offset map is keyed by column id. - Pinned columns should have a fixed
widthfor predictable offsets. Columns without a width default to100pxfor offset math. - The table's scroll container must be allowed to scroll horizontally. Don't set
overflow: hiddenon a parent. - Not compatible with
columnGroups— see above.
Prop reference
| Column prop | Type | Default | Description |
|---|---|---|---|
column.pinned | 'left' | 'right' | - | Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container — give columns explicit widths. |
CSS custom properties
| Variable | Default | Description |
|---|---|---|
--rdt-system-col-width | 48px | Width of the checkbox and expander columns. Pinning offsets read this so themes that customize the system column width stay aligned. |
--rdt-color-pin-border | theme divider color | Color of the 1px line on the inner edge of each pin band. |