Row pinning

Row pinning keeps specific rows anchored at the top of the table regardless of the active sort column or direction. Common uses: a "totals" row, a currently selected record, a high-priority item, or the signed-in user's own row.

Coming in a future release: native pinnedRows / pinnedRowIds props are on the roadmap. The patterns below are the recommended workaround until then — they compose cleanly with all existing features and are easy to swap out for the native API when it lands.

Pin rows to top

Click the pin icon on any row to anchor it. Pinned rows stay at position 1+ while the rest sort normally. Sort any column to see it in action.

Click the 📌 icon on any row to pin it. Pinned rows stay at the top regardless of how you sort. Sort any column to see the behaviour.

Name
Department
Status
Salary
Aria Chenpinned
Engineering
Active
$155,000
Jordan Ellispinned
Analytics
Active
$143,000
Alex Torres
Analytics
Active
$151,000
Casey Park
Design
Active
$109,000
Jamie Okonkwo
Sales
Terminated
$88,000
Marcus Webb
Product
Terminated
$132,000
Morgan Lee
Engineering
On Leave
$162,000
Priya Kapoor
Design
On Leave
$118,000
Sam Rivera
Engineering
Terminated
$128,000
Taylor Brooks
Sales
Active
$97,000

How it works

The DataTable-level sortFunction prop replaces the built-in sort algorithm entirely. It receives the complete row array, the selector function for whichever column is currently active, and the sort direction. Return the rows in the order you want them displayed.

Because sortFunction is called on every sort change, the pinned set just needs to live in React state — update the set and the rows re-order instantly without remounting the table.

Static pinned rows (hardcoded)

If the pinned rows are fixed (e.g. always show the "Totals" row first), define the set outside the component so it's stable and doesn't need useMemo:

const PINNED_IDS = new Set([42]);   // row with id 42 always floats to the top

const sortFunction: SortFunction<Employee> = (rows, selector, direction) => {
  const pinned = rows.filter(r => PINNED_IDS.has(r.id));
  const rest   = rows.filter(r => !PINNED_IDS.has(r.id));
  const sorted = [...rest].sort((a, b) => {
    const av = selector(a), bv = selector(b);
    if (av === bv) return 0;
    const cmp = av > bv ? 1 : -1;
    return direction === SortOrder.ASC ? cmp : -cmp;
  });
  return [...pinned, ...sorted];
};

<DataTable columns={columns} data={data} sortFunction={sortFunction} />

Pinning by a condition (not an ID)

You can pin based on any row property — not just an ID. For example, always show active employees before terminated ones, or float rows flagged as urgent:

// Always show 'Active' employees before others, then sort normally within each group
const sortFunction: SortFunction<Employee> = (rows, selector, direction) => {
  const pinned = rows.filter(r => r.status === 'Active');
  const rest   = rows.filter(r => r.status !== 'Active');

  const sort = (arr: Employee[]) =>
    [...arr].sort((a, b) => {
      const av = selector(a), bv = selector(b);
      if (av === bv) return 0;
      const cmp = av > bv ? 1 : -1;
      return direction === SortOrder.ASC ? cmp : -cmp;
    });

  return [...sort(pinned), ...sort(rest)];
};

Multiple pinned groups

Extend the pattern to produce any number of fixed groups. Each segment is sorted independently before being concatenated:

// Layout: [critical rows] → [normal rows, sorted] → [archived rows]
const sortFunction: SortFunction<Employee> = (rows, selector, direction) => {
  const critical = rows.filter(r => r.priority === 'critical');
  const archived = rows.filter(r => r.archived);
  const normal   = rows.filter(r => r.priority !== 'critical' && !r.archived);

  const sort = (arr: Employee[]) =>
    [...arr].sort((a, b) => {
      const av = selector(a), bv = selector(b);
      if (av === bv) return 0;
      const cmp = av > bv ? 1 : -1;
      return direction === SortOrder.ASC ? cmp : -cmp;
    });

  return [...sort(critical), ...sort(normal), ...sort(archived)];
};

Combining with server-side sorting

With sortServer, DataTable doesn't sort locally. Your onSort handler is responsible for returning the correct order. Apply pinning after the server response arrives, before setting state:

async function handleSort(col: TableColumn<Employee>, dir: SortOrder) {
  const sorted = await api.getEmployees({ sort: col.sortField, dir });

  // Re-apply pin order on top of server result
  const pinned = sorted.filter(r => pinnedIds.has(r.id));
  const rest   = sorted.filter(r => !pinnedIds.has(r.id));
  setData([...pinned, ...rest]);
}

Visual treatment

Pinned rows don't receive any special styling by default — add your own via conditionalRowStyles to make them visually distinct:

<DataTable
  columns={columns}
  data={data}
  sortFunction={sortFunction}
  conditionalRowStyles={[
    {
      when: row => pinnedIds.has(row.id),
      style: {
        borderLeft: '3px solid #7c3aed',
        backgroundColor: '#faf5ff',
        fontWeight: 500,
      },
    },
  ]}
/>

Limitations

  • This is a sort-layer workaround — rows are reordered by the sort function, not truly fixed in the DOM. If you use sortServer, you must apply pin order yourself after the fetch (see above).
  • Pinned rows participate in pagination. If a pinned row is on page 2, it won't appear on page 1 — pin logic only affects order within the current data array. For cross-page pinning, pre-sort your data array before passing it to data.
  • Native pinnedRows support (sticky DOM positioning, cross-page pinning, built-in styling) is planned for a future release.