Single and multi-column sorting configuration.
Sorting allows users to order data by one or multiple columns. The DataTable supports both single-column and multi-column sorting with customizable sort functions.
Sorting is enabled by default for all columns. Click a column header to sort, click again to reverse, and click a third time to clear sorting.
<DataTable
columns={columns}
data={data}
{...}
/>Disable sorting for all columns using column defaults:
<DataTable
columnDefaults={{
enableSorting: false,
}}
{...}
/>Control sorting at the column level:
const columns: DataTableColumnDef<User>[] = [
{
accessorKey: 'name',
header: 'Name',
enableSorting: true, // Sortable
},
{
accessorKey: 'id',
header: 'ID',
enableSorting: false, // Not sortable
},
];By default, only one column can be sorted at a time. Sorting a new column clears the previous sort.
// Click "Name" header -> sorts by name ascending
// Click "Name" again -> sorts by name descending
// Click "Name" again -> clears sort
// Click "Email" header -> sorts by email, clears name sortEnable multi-column sorting by holding Shift while clicking column headers.
// Click "Name" -> sorts by name
// Shift+Click "Email" -> sorts by name, then email
// Shift+Click "Status" -> sorts by name, then email, then statusThe sort order is indicated by numbered badges on sorted columns.
{
accessorKey: 'status',
header: 'Status',
sortingFn: (rowA, rowB, columnId) => {
const statusOrder = ['pending', 'active', 'completed'];
const valueA = rowA.getValue(columnId) as string;
const valueB = rowB.getValue(columnId) as string;
return statusOrder.indexOf(valueA) - statusOrder.indexOf(valueB);
},
}{
accessorKey: 'name',
header: 'Name',
sortingFn: (rowA, rowB, columnId) => {
const valueA = (rowA.getValue(columnId) as string).toLowerCase();
const valueB = (rowB.getValue(columnId) as string).toLowerCase();
return valueA.localeCompare(valueB);
},
}{
accessorKey: 'orderNumber',
header: 'Order #',
sortingFn: (rowA, rowB, columnId) => {
const numA = parseInt(rowA.getValue(columnId) as string);
const numB = parseInt(rowB.getValue(columnId) as string);
return numA - numB;
},
}{
accessorKey: 'createdAt',
header: 'Created',
sortingFn: (rowA, rowB, columnId) => {
const dateA = new Date(rowA.getValue(columnId) as string).getTime();
const dateB = new Date(rowB.getValue(columnId) as string).getTime();
return dateA - dateB;
},
}{
accessorKey: 'user',
header: 'User',
sortingFn: (rowA, rowB, columnId) => {
const userA = rowA.getValue(columnId) as User;
const userB = rowB.getValue(columnId) as User;
return userA.lastName.localeCompare(userB.lastName);
},
}import { useDataTable } from '@workspace/data-table';
function MyComponent() {
const table = useDataTable();
// Set single sort
const sortByName = () => {
table.setSorting([{ id: 'name', desc: false }]);
};
// Set multi sort
const sortByMultiple = () => {
table.setSorting([
{ id: 'status', desc: false },
{ id: 'createdAt', desc: true },
]);
};
// Clear sorting
const clearSort = () => {
table.setSorting([]);
};
// Toggle column sort
const toggleNameSort = () => {
const column = table.getColumn('name');
column?.toggleSorting();
};
}import { useDataTable } from '@workspace/data-table';
function MyComponent() {
const table = useDataTable();
const sortingState = table.getState().sorting;
// sortingState: [{ id: 'name', desc: false }]
return (
<div>
{sortingState.length > 0 ? (
<p>Sorted by: {sortingState.map(s => s.id).join(', ')}</p>
) : (
<p>No sorting applied</p>
)}
</div>
);
}Set initial sorting when the table loads:
For server-side mode, sorting state is passed to your fetch function:
The grid automatically displays sort indicators in column headers:
For large datasets (>1000 rows), consider:
import { useState } from 'react';
import { SortingState } from '@tanstack/react-table';
function MyComponent() {
const [sorting, setSorting] = useState<SortingState>([
{ id: 'createdAt', desc: true }, // Sort by date descending on load
]);
return (
<DataTable
columns={columns}
data={data}
state={{ sorting }}
onSortingChange={setSorting}
{...}
/>
);
}<DataTable
mode="server"
fetchFn={async (params) => {
// params.sorting: [{ id: 'name', desc: false }]
const response = await api.getUsers({
sortBy: params.sorting[0]?.id,
sortDir: params.sorting[0]?.desc ? 'desc' : 'asc',
});
return {
data: response.data,
totalCount: response.total,
};
}}
{...}
/>{
accessorKey: 'name',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting()}
>
Name
{column.getIsSorted() === 'asc' && <ArrowUp className="ml-2 h-4 w-4" />}
{column.getIsSorted() === 'desc' && <ArrowDown className="ml-2 h-4 w-4" />}
</Button>
);
},
}{
accessorKey: 'priority',
header: 'Priority',
sortingFn: (rowA, rowB, columnId) => {
const priorityOrder = {
critical: 0,
high: 1,
medium: 2,
low: 3,
};
const valueA = rowA.getValue(columnId) as keyof typeof priorityOrder;
const valueB = rowB.getValue(columnId) as keyof typeof priorityOrder;
return priorityOrder[valueA] - priorityOrder[valueB];
},
}{
accessorKey: 'lastLogin',
header: 'Last Login',
sortingFn: (rowA, rowB, columnId) => {
const valueA = rowA.getValue(columnId) as string | null;
const valueB = rowB.getValue(columnId) as string | null;
// Push nulls to bottom
if (!valueA && !valueB) return 0;
if (!valueA) return 1;
if (!valueB) return -1;
return new Date(valueA).getTime() - new Date(valueB).getTime();
},
}{
accessorKey: 'version',
header: 'Version',
sortingFn: (rowA, rowB, columnId) => {
const versionA = rowA.getValue(columnId) as string;
const versionB = rowB.getValue(columnId) as string;
const partsA = versionA.split('.').map(Number);
const partsB = versionB.split('.').map(Number);
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const a = partsA[i] || 0;
const b = partsB[i] || 0;
if (a !== b) return a - b;
}
return 0;
},
}import { SortingFn } from '@tanstack/react-table';
type Order = {
id: string;
status: 'pending' | 'active' | 'completed';
amount: number;
};
const statusSort: SortingFn<Order> = (rowA, rowB, columnId) => {
const statusOrder = ['pending', 'active', 'completed'];
const valueA = rowA.getValue(columnId) as Order['status'];
const valueB = rowB.getValue(columnId) as Order['status'];
return statusOrder.indexOf(valueA) - statusOrder.indexOf(valueB);
};
const columns: DataTableColumnDef<Order>[] = [
{
accessorKey: 'status',
header: 'Status',
sortingFn: statusSort,
},
];