Documentation
Concepts

Sorting

Sortable columns toggled from the header by mouse or keyboard, the sort kept by the grid, the rows ordered by your app.

The grid keeps which columns are sorted, in which direction and in which order, toggles them from the header and shows them in ARIA and data-*. It never reorders your rows: you sort what you pass, in the browser or on your server.

Sorting
Gallery

Sortable columns

A column with sortable: true sorts the grid. A group never does: its columns can.

columns.tsx
const columns: Column<Person>[] = [
    { key: "name", name: "Name", width: 180, sortable: true },
    { key: "email", name: "Email", width: 260 },
    { key: "salary", name: "Salary", width: 130, sortable: true },
];

The sort

The sort is a list of { columnKey, direction }, the first column first; direction is "ascending" or "descending", the values of aria-sort. Like the active cell, it is controlled or not:

  • sortColumns with onSortColumnsChange: a change is asked for, and only the prop applies it;
  • defaultSortColumns: the grid starts from it, applies changes and tells onSortColumnsChange.
people.tsx
const [sortColumns, setSortColumns] = useState<readonly SortColumn[]>([]);
const rows = useMemo(() => sortRows(people, sortColumns), [sortColumns]);

<DataGrid.Root
    columns={columns}
    rows={rows}
    sortColumns={sortColumns}
    onSortColumnsChange={setSortColumns}
>
    {/* … */}
</DataGrid.Root>

A sort naming a column that is not sortable keeps the rest: the grid leaves that entry out and tells onSortColumnsChange. When the columns change, a sorted column that is gone (or no longer sortable) leaves the sort the same way.

From the header

A click on a sortable column's header cell, or Enter or Space while it is the active cell, toggles it: ascending, descending, not sorted. Alone, the column becomes the only sorted one; with Ctrl (⌘ on a Mac), it is added last, or cycles in place, and the others stay.

The grid acts after your handlers, as for its keys: preventDefault in an onClick or onKeyDown on the header cell, on Root or on its render element cancels the toggle. A click or a key on a control inside the header cell (a button, a link, a menu trigger, a popover) is the control's, never a sort; neither is a drag across the header's text, nor a held key repeating.

The same changes go through the model, for a toolbar or a menu of your own: model.run("sort-columns.toggle", { columnKey: "name", multi: true }), model.run("sort-columns.set", { sortColumns: [] }), reached with useDataGrid() or a gridRef. A middleware can refuse or rewrite them.

What it shows

on a header cellwhen
data-sortableits column is sortable
data-sortascending or descending, while its column is sorted
data-sort-priorityits column's place in the sort, 1-based
aria-sorton the first sorted column only

ARIA 1.2 asks for aria-sort on one header at a time, so a sort on several columns announces its first one; the others are in data-sort. Indicators are yours: draw an arrow from data-sort in CSS, or from the header cell's state (sortable, sortDirection, sortPriority) in a render function, as the example does.

Rows stay yours

The grid shows the rows it is given, in that order: sorting them is your app's, so a server-side sort works the same as an in-memory one. The active cell keeps its position when the rows change order: it stays on the same row index and column, not on the same record.