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 use
position: sticky and render a subtle directional shadow on the band edge to
indicate the boundary between sticky and scrolling content.
Column pinning
Name is pinned to the left, Status is pinned to the right. Scroll the table horizontally. Both stay in place. Resize any column and the pin offsets update automatically.
Name is pinned left, Status is pinned right. Drag middle column headers to reorder, resize any column from its right edge.
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 /> How it works
The table measures the widths of all pinned columns and computes cumulative
left or right CSS offsets so pinned columns stack correctly when
more than one is pinned on the same side. Header and body cells both receive
position: 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 at
left: 80px (the width of ID). On the right side it's mirrored:
Action sticks at right: 0 and Status at
right: 64px.
With selectable rows or expandable rows
The checkbox and expander columns are always pinned to the left edge — they don't need
pinned 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: 2
versus 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 pinned
from 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 shadow
The directional shadow is drawn on the inner edge of each pin band — class
rdt_pinLeftLast on the rightmost left-pinned column and
rdt_pinRightFirst on the leftmost right-pinned column. Override the color via
the --rdt-color-pin-shadow CSS variable:
.rdt_table {
--rdt-color-pin-shadow: rgba(0, 0, 0, 0.2); /* darker shadow */
} 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 shadow) 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 |
|---|---|---|---|
pinned | 'left' | 'right' | - | Pin the column to the left or right edge. Requires an explicit id. |
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-shadow | rgba(0, 0, 0, 0.12) | Color of the directional shadow on the inner edge of each pin band. |