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).
When to use
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>.