Skip to content

Components

A Sortable React Data Table Without TanStack — Columns as Data, Not JSX

One Column Array, One Sort Comparator, One aria-sort Attribute
Written by Aiden Wood Founder and Lead Design Engineer
🗂️ ↕️ ☑️

TanStack Table is a genuinely great headless library — for virtualised, grouped, pivoted, server-paginated grids. Most admin pages need a table that sorts when you click a header and lets you tick rows. Reaching for the full abstraction there is renting a crane to hang a photo.

Velocity X's standard-sortable-data-table is the ~200-line version: sortable columns, row selection, sticky header, empty state. One file, readable in a sitting, restyled with your Tailwind config because it is your file.

The Money Pattern

The design decision that makes a table maintainable: columns are data, not JSX. The table maps over a definition array, and sorting is a property of the column:

type Column<T> = {
  key: string;
  label: string;
  sort?: (a: T, b: T) => number;
  render?: (row: T) => ReactNode;
};

const columns: Column<User>[] = [
  { key: 'name', label: 'Name', sort: (a, b) => a.name.localeCompare(b.name) },
  { key: 'mrr', label: 'MRR', sort: (a, b) => a.mrr - b.mrr,
    render: (u) => formatCurrency(u.mrr) },
  { key: 'actions', label: '', render: (u) => <RowMenu id={u.id} /> },
];

Click handling is then trivial: same column toggles direction, new column resets to ascending, and the sorted array is a useMemo over [rows, sortKey, dir]. No reducer, no table "instance", no plugin system.

The Catch

Two details sort (sorry) the professionals from the demos. First, aria-sort on the active header — "ascending" / "descending", removed from the others. Without it the sort state is invisible to assistive tech, and it's one attribute.

Second, select-all is a three-state checkbox: none, some (indeterminate), all. The indeterminate property can only be set via a ref — it's not an attribute — which is exactly the kind of thing you want already handled in a file you own rather than rediscovered at 6pm.

And know when you've outgrown it: 10k+ rows needs virtualisation, server pagination needs server sort. That's when TanStack earns its abstraction — upgrade then, not pre-emptively.

The Verdict

Columns as data, comparator per column, aria-sort, three-state select-all — that's the entire table. The finished file lives in Velocity X's Standard Data category beside the editable, grouped and tree-table variants when the requirements grow teeth.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.