Sorting
Click a column header to sort. Click again to reverse. Click a third time to clear. An arrow shows the current direction. That's the whole interaction — no configuration beyond sortable: true.
The demo uses Radix UI styling. Your framework's design system (Fluent, Material, Vuetify, PrimeNG) will render the sort indicator in its own style.
Make a column sortable
Add sortable: true to any column definition:
- React
- Angular
- Vue
- Vanilla JS
import { OGrid } from '@alaarab/ogrid-react-radix';
const columns = [
{ columnId: 'name', name: 'Name', sortable: true },
{ columnId: 'age', name: 'Age', sortable: true, type: 'numeric' as const },
{ columnId: 'department', name: 'Department', sortable: true },
{
columnId: 'salary',
name: 'Salary',
sortable: true,
type: 'numeric' as const,
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
},
];
function App() {
return (
<OGrid
columns={columns}
data={people}
getRowId={(item) => item.id}
defaultPageSize={10}
/>
);
}
Same props across all React packages — just change the import:
- Radix (lightweight, default):
from '@alaarab/ogrid-react-radix' - Fluent UI (Microsoft 365 / SPFx):
from '@alaarab/ogrid-react-fluent'- wrap in<FluentProvider> - Material UI (MUI v7):
from '@alaarab/ogrid-react-material'- wrap in<ThemeProvider>
import { Component } from '@angular/core';
import { OGridComponent, type IColumnDef } from '@alaarab/ogrid-angular-material';
@Component({
standalone: true,
imports: [OGridComponent],
template: `<ogrid [props]="gridProps" />`
})
export class GridComponent {
gridProps = {
columns: [
{ columnId: 'name', name: 'Name', sortable: true },
{ columnId: 'age', name: 'Age', sortable: true, type: 'numeric' },
{ columnId: 'department', name: 'Department', sortable: true },
{
columnId: 'salary',
name: 'Salary',
sortable: true,
type: 'numeric',
valueFormatter: (v: unknown) => `$${Number(v).toLocaleString()}`,
},
] as IColumnDef<Person>[],
data: people,
getRowId: (item: Person) => item.id,
defaultPageSize: 10,
};
}
Same API across Angular packages. Change the import:
- Radix (CDK):
from '@alaarab/ogrid-angular-radix'(default, lightweight) - Angular Material:
from '@alaarab/ogrid-angular-material' - PrimeNG:
from '@alaarab/ogrid-angular-primeng'
All components are standalone — no NgModule required.
<script setup lang="ts">
import { OGrid, type IColumnDef } from '@alaarab/ogrid-vue-vuetify';
const columns: IColumnDef<Person>[] = [
{ columnId: 'name', name: 'Name', sortable: true },
{ columnId: 'age', name: 'Age', sortable: true, type: 'numeric' },
{ columnId: 'department', name: 'Department', sortable: true },
{
columnId: 'salary',
name: 'Salary',
sortable: true,
type: 'numeric',
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
},
];
const gridProps = {
columns,
data: people,
getRowId: (item) => item.id,
defaultPageSize: 10,
};
</script>
<template>
<OGrid :gridProps="gridProps" />
</template>
Same API across Vue packages. Change the import:
- Radix (Headless UI):
from '@alaarab/ogrid-vue-radix'(default, lightweight) - Vuetify:
from '@alaarab/ogrid-vue-vuetify'- wrap in<v-app>for theming - PrimeVue:
from '@alaarab/ogrid-vue-primevue'
import { OGrid } from '@alaarab/ogrid-js';
import '@alaarab/ogrid-js/styles';
const grid = new OGrid(document.getElementById('grid'), {
columns: [
{ columnId: 'name', name: 'Name', sortable: true },
{ columnId: 'age', name: 'Age', sortable: true, type: 'numeric' },
{ columnId: 'department', name: 'Department', sortable: true },
{
columnId: 'salary',
name: 'Salary',
sortable: true,
type: 'numeric',
valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
},
],
data: people,
getRowId: (item) => item.id,
pageSize: 10,
});
Set a default sort
Use defaultSortBy and defaultSortDirection to pre-sort the grid on mount — useful when there's an obvious "right" order for your data, like newest-first for a log or alphabetical for a directory:
<OGrid
columns={columns}
data={employees}
getRowId={(item) => item.id}
defaultSortBy="name"
defaultSortDirection="asc"
/>
Custom sort order
By default, text columns sort alphabetically and numeric columns sort numerically. When that isn't right — priority levels, custom status orderings, anything domain-specific — provide a compare function:
const columns = [
{
columnId: 'priority',
name: 'Priority',
sortable: true,
// "High" should sort before "Medium" which sorts before "Low"
compare: (a, b) => {
const order = { High: 0, Medium: 1, Low: 2 };
return order[a.priority] - order[b.priority];
},
},
{
columnId: 'status',
name: 'Status',
sortable: true,
// Custom business logic: "In Progress" first, then "Open", then "Closed"
compare: (a, b) => {
const order = { 'In Progress': 0, Open: 1, Closed: 2 };
return (order[a.status] ?? 99) - (order[b.status] ?? 99);
},
},
];
Custom compare functions receive the full row objects — not just the cell values. That means you can sort by computed values, nested fields, or anything else you can derive from the row.
Keep sort state in your app
If you need to sync sort state to the URL, read it from an API, or share it between components, use controlled mode:
function App() {
const [sort, setSort] = useState({ field: 'name', direction: 'asc' as const });
// Persist to URL:
// useEffect(() => { setSearchParam('sort', sort.field); }, [sort]);
return (
<OGrid
columns={columns}
data={people}
getRowId={(item) => item.id}
sort={sort}
onSortChange={setSort}
/>
);
}
When sort and onSortChange are both present, the grid is fully controlled — it never manages sort state internally.
Server-side sorting
Sending sort state to your API is straightforward. The grid passes sort info to fetchPage() — your server does the actual ordering:
const dataSource: IDataSource<Project> = {
fetchPage: async ({ sort, page, pageSize, filters }) => {
const params = new URLSearchParams({
page: String(page),
pageSize: String(pageSize),
});
// Wire sort directly to your query params or request body
if (sort) {
params.set('sortBy', sort.field);
params.set('sortDir', sort.direction); // 'asc' | 'desc'
}
const res = await fetch(`/api/projects?${params}`);
const json = await res.json();
return { items: json.data, totalCount: json.total };
},
};
Server-side sorting disables client-side sort entirely — the grid just re-fetches when the user clicks a header. This pairs perfectly with large datasets where you never want all rows in the browser at once. See Server-Side Data for the full setup.
Props reference
| Prop | Type | Default | Description |
|---|---|---|---|
sortable | boolean | false | Set on IColumnDef. Enables click-to-sort on that column header. |
compare | (a: T, b: T) => number | — | Custom sort comparator on IColumnDef. Receives full row objects. |
sort | { field: string; direction: 'asc' | 'desc' } | — | Controlled sort state on OGrid. |
onSortChange | (sort) => void | — | Called when the user changes the sort. Required for controlled mode. |
defaultSortBy | string | — | Initial sort column (uncontrolled only). |
defaultSortDirection | 'asc' | 'desc' | 'asc' | Initial sort direction (uncontrolled only). |
Next steps
- Filtering — filter rows before sorting
- Pagination — paginate sorted results
- Performance — offload sort to a Web Worker for large datasets