Sort Indicator

States a table's sort in plain words and warns when hidden columns, empty values or pinned rows make the order look wrong.

StopsMisread the table's sort order

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/sort-indicator.json

The files land in your project. They're yours to edit.

Usage

sort-indicator-usage.tsx
import { ariaSort, SortButton, SortIndicator, sortRows, useFlip, type SortColumn, type SortState } from "@/components/ui/sort-indicator"

<SortIndicator
  columns={columns}
  value={sort}
  onValueChange={setSort}
  emptyCount={sorted.emptyCount}
  pinnedCount={sorted.pinnedCount}
  onRevealColumn={() => setShowPriority(true)}
  rowWords={["task", "tasks"]}
/>

What it catches

  • Sort by a column that isn't on screen, with a reveal action
  • Empty values, kept last in both directions and counted
  • Pinned rows that stay first regardless of sort
  • Direction in words that fit the column (A to Z, oldest first)

Props

SortIndicator props
PropTypeDefault
columns* (required)

All sortable columns, used for labels and the readback.

SortColumn[]—
value

Controlled sort; `null` means unsorted.

SortState | null—
defaultValue

Starting sort when uncontrolled.

SortState | nullnull
onValueChange

Called with the new sort, or null when cleared.

(sort: SortState | null) => void—
emptyCount

Rows with no value in the sort column (they sit at the end). `sortRows` returns it.

number0
pinnedCount

Rows pinned above the sort. `sortRows` returns it.

number0
onRevealColumn

Shows a "Show column" fix when sorting by a hidden column.

(columnId: string) => void—
sortedText

Templates: `{label}`, `{direction}`, `{count}`, `{rows}` are filled in.

string"Sorted by {label}, {direction}"
unsortedText

Readback when nothing is sorted.

string"Not sorted: rows are in the order they were added"
reverseLabel

Label of the reverse button.

string"Reverse"
clearLabel

Label of the clear-sort button.

string"Clear sort"
hiddenText

Warning when sorting by a hidden column.

string"{label} isn't shown in the table, so the order can look …
revealLabel

Label of the reveal-column button.

string"Show {label}"
emptyText

Note about rows with empty values.

string"{count} {rows} with no {label} placed at the end."
pinnedText

Note about pinned rows.

string"{count} pinned {rows} kept on top whatever the sort."
rowWords

Singular and plural word for a row, e.g. ["task", "tasks"].

[string, string]["row", "rows"]

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Header button with a descriptive title; use the ariaSort helper for aria-sort on <th>
  • Sort readback and warnings render in polite live regions
  • Row animation (FLIP) is skipped under reduced motion
  • Reverse and clear are labelled buttons

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • Sorting helper uses natural numeric comparison; locale-specific collation may differ
  • You place aria-sort on your <th> (ariaSort helper provided)

What it stops

Misread the table's sort order

Also known as

table sort · sorting · data table · column header · asc desc · tanstack table · ag grid · sort arrow

Dependencies

Pairs well with