React Grid Column Pinning: Implementation Guide & Best Practices

Column PinningTutorialBest Practices

Keep critical columns visible while users scroll through wide datasets. Learn how to implement column pinning in React data grids with practical examples and implementation patterns.

You're reviewing sales data from the past quarter. There are 30+ columns of metrics—conversion rates, revenue per channel, customer acquisition costs, regional breakdowns. You scroll right to see Q3 data, and suddenly you've lost context. Which customer is this row about? What's the deal ID?

This is why column pinning (also called "freezing" or "sticky columns") exists. It keeps key columns—like IDs, names, or action buttons—fixed in place while the rest of the table scrolls horizontally. Users never lose context, no matter how wide your data gets.

In this guide, we'll cover how to implement column pinning in React data grids, common pitfalls to avoid, and best practices for deciding what to pin. Whether you're building financial dashboards, CRM tools, or admin panels, column pinning is essential for working with wide datasets.

Why Column Pinning Matters

Wide tables are everywhere in business software. Financial reports span quarters. Product catalogs have dozens of specs. User tables track countless attributes. Without column pinning, users face a frustrating choice: sacrifice screen space to see everything, or lose context when scrolling.

Common Use Cases

Financial Dashboards

Pin the account name and ID while scrolling through quarterly revenue, expenses, and profit across multiple periods.

CRM & Sales Tools

Keep contact name and company visible while viewing deal stage, value, probability, and activity history across wide tables.

E-Commerce Admin

Pin product name and SKU while managing inventory, pricing, variants, and shipping details across multiple warehouses.

Action Columns

Pin action buttons (Edit, Delete, View) to the right edge so they're always accessible, no matter where users scroll.

The Implementation Challenge

Column pinning sounds simple: just add position: sticky to some columns, right? Unfortunately, the CSS-only approach falls apart quickly. This is why professional data grids like AG Grid and Simple Table handle it for you:

CSS-Only Approach Problems

  • Stacking context issues: Z-index wars with other sticky elements like headers or filters
  • Shadow/border gaps: Visual artifacts where pinned columns meet scrolling content
  • Browser inconsistencies: Different rendering behavior across Chrome, Firefox, Safari
  • Touch device quirks: Sticky columns behave unpredictably on mobile/tablet

Library-Based Solution Benefits

  • Tested across browsers: Works consistently in all modern browsers
  • Handles edge cases: Column resizing, reordering, and dynamic visibility all work together
  • Proper shadows/borders: Visual indicators show where pinned columns end
  • Mobile-optimized: Touch-friendly behavior on all devices

How to Implement Column Pinning in Simple Table

With Simple Table, column pinning is refreshingly simple. Add a pinned property to any column header, and it stays fixed while the rest scrolls.

Basic Example: CRM Dashboard

React TSX
1import { SimpleTable, HeaderObject } from "simple-table-core";
2import "simple-table-core/styles.css";
3
4const headers: HeaderObject[] = [
5 {
6 accessor: "id",
7 label: "ID",
8 width: 80,
9 pinned: "left", // Pin to the left
10 isSortable: true,
11 type: "number",
12 },
13 {
14 accessor: "name",
15 label: "Contact Name",
16 width: 200,
17 pinned: "left", // Also pin to the left
18 isSortable: true,
19 type: "string",
20 },
21 {
22 accessor: "company",
23 label: "Company",
24 width: 180,
25 isSortable: true,
26 type: "string",
27 },
28 {
29 accessor: "dealStage",
30 label: "Deal Stage",
31 width: 150,
32 isSortable: true,
33 type: "string",
34 },
35 {
36 accessor: "dealValue",
37 label: "Deal Value",
38 width: 120,
39 isSortable: true,
40 type: "number",
41 align: "right",
42 valueFormatter: ({ value }) =>
43 new Intl.NumberFormat('en-US', {
44 style: 'currency',
45 currency: 'USD'
46 }).format(value as number),
47 },
48 {
49 accessor: "probability",
50 label: "Probability",
51 width: 100,
52 isSortable: true,
53 type: "number",
54 align: "right",
55 },
56 {
57 accessor: "lastContact",
58 label: "Last Contact",
59 width: 140,
60 isSortable: true,
61 type: "date",
62 },
63 {
64 accessor: "actions",
65 label: "Actions",
66 width: 120,
67 pinned: "right", // Pin to the right
68 align: "center",
69 cellRenderer: ({ row }) => (
70 <div className="flex gap-2 justify-center">
71 <button className="text-blue-600 hover:text-blue-800">Edit</button>
72 <button className="text-green-600 hover:text-green-800">View</button>
73 </div>
74 ),
75 },
76];
77
78export default function CRMTable({ data }) {
79 return (
80 <SimpleTable
81 defaultHeaders={headers}
82 rows={data}
83 rowIdAccessor="id"
84 height="500px"
85 theme="light"
86 />
87 );
88}

That's it! The ID, Contact Name, and Actions columns stay fixed. Users can scroll through all the deal details while always knowing which contact they're looking at and having quick access to actions.

Key Points

  • Left pinning: Use pinned: "left" for identity columns (ID, name, title)
  • Right pinning: Use pinned: "right" for action columns (Edit, Delete, View)
  • Multiple columns: You can pin multiple columns to the same side—they stack in order
  • Works with other features: Pinned columns support sorting, filtering, custom renderers, and all other column features

Column Pinning Best Practices

What to Pin

Good Candidates for Pinning

  • Identity columns: ID, name, title, email
  • Primary context: Customer name, account ID, order number
  • Action buttons: Edit, Delete, View, More
  • Status indicators: Active/Inactive, Deal Stage, Priority
  • Selection checkboxes: For bulk operations

Avoid Pinning These

  • Wide columns: Long descriptions or multi-paragraph content
  • Optional metadata: Created date, last updated, internal notes
  • Too many columns: Don't pin more than 2-3 on each side
  • Columns users rarely need: Advanced technical details
  • Redundant information: If it's also in a detail view

Performance & UX Guidelines

📏 Keep Pinned Columns Narrow

Pinned columns consume valuable horizontal space. Aim for 200-300px total for left-pinned columns, 120-150px for right-pinned actions. Users should see unpinned content without scrolling.

🎯 Pin What's Essential, Not Everything

If you pin 10 columns, users can't see any scrolling content. Pin only what provides context or critical actions. Aim for 1-3 pinned columns max per side.

📱 Test on Mobile & Tablets

On small screens, pinned columns consume a larger percentage of viewport width. Consider conditionally disabling pinning on mobile, or only pin 1 column instead of 2-3.

💡 Use Visual Indicators

Simple Table automatically adds shadows where pinned columns meet scrolling content, helping users understand the layout. Don't remove these—they're critical for usability.

Advanced Patterns

Conditional Pinning (Mobile-Responsive)

You can conditionally pin columns based on screen size using responsive header configuration:

React TSX
1import { useMediaQuery } from "react-responsive";
2
3export default function ResponsiveTable({ data }) {
4 const isMobile = useMediaQuery({ maxWidth: 768 });
5
6 const headers: HeaderObject[] = [
7 {
8 accessor: "id",
9 label: "ID",
10 width: 60,
11 pinned: isMobile ? undefined : "left", // Only pin on desktop
12 },
13 {
14 accessor: "name",
15 label: "Name",
16 width: 180,
17 pinned: "left", // Always pinned
18 },
19 // ... other columns
20 {
21 accessor: "actions",
22 label: "Actions",
23 width: 100,
24 pinned: isMobile ? undefined : "right", // Only pin on desktop
25 },
26 ];
27
28 return <SimpleTable defaultHeaders={headers} rows={data} />;
29}

User-Configurable Pinning

For power users, let them choose which columns to pin:

React TSX
1import { useState } from "react";
2
3export default function ConfigurableTable({ data }) {
4 const [pinnedColumns, setPinnedColumns] = useState<string[]>(["id", "name"]);
5
6 const headers: HeaderObject[] = [
7 {
8 accessor: "id",
9 label: "ID",
10 width: 60,
11 pinned: pinnedColumns.includes("id") ? "left" : undefined,
12 },
13 {
14 accessor: "name",
15 label: "Name",
16 width: 180,
17 pinned: pinnedColumns.includes("name") ? "left" : undefined,
18 },
19 // ... other columns
20 ];
21
22 const togglePin = (accessor: string) => {
23 setPinnedColumns((prev) =>
24 prev.includes(accessor)
25 ? prev.filter((col) => col !== accessor)
26 : [...prev, accessor]
27 );
28 };
29
30 return (
31 <div>
32 <div className="mb-4">
33 <strong>Pin/Unpin Columns:</strong>
34 <button onClick={() => togglePin("id")}>
35 {pinnedColumns.includes("id") ? "Unpin" : "Pin"} ID
36 </button>
37 <button onClick={() => togglePin("name")}>
38 {pinnedColumns.includes("name") ? "Unpin" : "Pin"} Name
39 </button>
40 </div>
41 <SimpleTable defaultHeaders={headers} rows={data} />
42 </div>
43 );
44}

Column Pinning Across React Libraries

How does column pinning compare across popular React data grid libraries?

LibraryPinning SupportAPI ComplexityCost
Simple Table✓ Built-inSimple (1 prop)Free
AG Grid Community✗ Enterprise OnlyN/A$999+/dev/year
TanStack Table⚡ HeadlessComplex (build UI)Free
Ant Design Table✓ Built-in (fixed)SimpleFree
Material React Table✓ Built-inMedium (config)Free

Wrap Up: Column Pinning Done Right

Column pinning is essential for working with wide datasets. It keeps users oriented, reduces cognitive load, and makes action buttons always accessible. The implementation doesn't have to be complex—with the right library, it's as simple as adding pinned: "left" or pinned: "right".

  • Pin identity columns (ID, name) to the left
  • Pin action buttons to the right for easy access
  • Keep pinned columns narrow (200-300px total)
  • Test on mobile and adjust pinning for small screens
  • Don't over-pin—aim for 1-3 columns per side maximum

Whether you're building financial dashboards, CRM tools, or e-commerce admin panels, column pinning improves UX dramatically. With Simple Table, you get production-ready pinning without the complexity—just one prop and you're done.

Ready to add column pinning to your React app?

Simple Table makes column pinning effortless with one simple prop. No CSS hacks, no browser inconsistencies, no complexity. Just production-ready pinning that works everywhere.