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.

Name
Role
Department
Location
Salary
Status
Aria Chen
Engineering Lead
Engineering
San Francisco
$155,000
Active
Marcus Webb
Product Manager
Product
New York
$132,000
Active
Priya Kapoor
Senior Designer
Design
Austin
$118,000
Active
Jordan Ellis
Data Scientist
Analytics
Seattle
$143,000
On Leave
Sam Rivera
DevOps Engineer
Engineering
Remote
$128,000
Active
Taylor Kim
Frontend Engineer
Engineering
Chicago
$122,000
Active
Alex Morgan
QA Engineer
Engineering
Denver
$108,000
Active
Casey Nguyen
Scrum Master
Product
Boston
$115,000
Active

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 pinned stripped.

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 width still 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 width for predictable offsets. Columns without a width default to 100px for offset math.
  • The table's scroll container must be allowed to scroll horizontally. Don't set overflow: hidden on a parent.
  • Not compatible with columnGroups — see above.

Prop reference

Column propTypeDefaultDescription
pinned 'left' | 'right' - Pin the column to the left or right edge. Requires an explicit id.

CSS custom properties

VariableDefaultDescription
--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.