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.

Pin left:
Pin right:

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.

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

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

VariableDefaultDescription
--rdt-system-col-width48pxWidth of the checkbox and expander columns. Pinning offsets read this so themes that customize the system column width stay aligned.
--rdt-color-pin-bordertheme divider colorColor of the 1px line on the inner edge of each pin band.