Skip to content

SortableTableHead

<SortableTableHead> is a sortable column header. It renders the click target + caret + aria-sort and fires a click handler — the caller owns the sort state and cycles it (asc → desc → null → asc per Attio convention).

import from "@na/ui/components/SortableTableHead" ▶ Open in Storybook packages/ui/src/components/SortableTableHead.tsx

When to use

  • Sortable column headers in Table (or DataTable — which uses this internally).

Don’t use SortableTableHead for: non-sortable columns (use TableHead directly), pagination (TablePagination), or selection.

Default

The 3-state cycler: ↕ (sortable, not active) → ↑ (asc) → ↓ (desc) → ↕ (back to default).

Acme Corp$24,000
Globex$8,500
Initech$42,000
Soylent$12,000
const [sort, setSort] = useState<{ col: string; dir: SortDirection }>({ col: 'name', dir: 'asc' });
const cycle = (col: string) => setSort((prev) => {
if (prev.col !== col) return { col, dir: 'asc' };
if (prev.dir === 'asc') return { col, dir: 'desc' };
if (prev.dir === 'desc') return { col, dir: null };
return { col, dir: 'asc' };
});
<SortableTableHead
sortDirection={sort.col === 'name' ? sort.dir : null}
onSort={() => cycle('name')}
>
Name
</SortableTableHead>

API

Prop Type Default Description
sortDirection "asc" | "desc" | null null This column's active sort. null = sortable but inactive.
onSort () => void Click handler. Caller cycles state.
children * ReactNode Column label.
className string Forwarded.
...rest ThHTMLAttributes All native <th> attributes.

Design guidelines

✓ Do

  • Pass sortDirection={null} for non-active sortable columns — the caret stays muted, signaling sortability.
  • Use the 3-state cycle (asc → desc → null) so users can return to the natural order.
  • Persist the last sort in URL state so reload preserves the user's view.

✗ Don't

  • Mix SortableTableHead with non-sortable plain TableHead in inconsistent order. Group sortable columns together.
  • Cycle through asc → desc → asc forever. The null state is critical — users want to see the natural order.

Accessibility

  • Sets aria-sort="ascending" / "descending" / "none" on the <th>.
  • Click target is keyboard-reachable as a real <button> inside the <th>.
  • Table — Underlying primitive.
  • DataTable — Composes SortableTableHead automatically when columns have sortable: true.

▶ Open SortableTableHead stories in Storybook