Server-side sort
Sortable column headers whose href encodes the sort state. Click to toggle; the server is the source of truth.
#When to use
Tables where the dataset is too large to sort in memory, or where the sort key is a computed column. For small, purely client-visible sorts, consider Alpine's Array.prototype.sort over the bound array instead.
#Markup
<th>
<a hx-get="/products?sort=price&dir=@(ViewData["NextDir"])"
hx-target="#products_table"
hx-swap="outerHTML"
hx-push-url="true"
href="/products?sort=price&dir=@(ViewData["NextDir"])">
Price
@if (ViewData["Sort"] as string == "price")
{
<span>@(ViewData["Dir"] as string == "asc" ? "▲" : "▼")</span>
}
</a>
</th>
hx-push-url="true" keeps the URL in sync so the sort state survives refresh and back-navigation.
#Handler
public async Task<IActionResult> OnGetAsync(string sort = "name", string dir = "asc")
{
var results = await _products.ListAsync(sort, dir);
ViewData["Sort"] = sort;
ViewData["Dir"] = dir;
ViewData["NextDir"] = dir == "asc" ? "desc" : "asc";
return this.PageOrPartial("_ProductsTable", results);
}
#DataTable component
The ndui add datatable component wires this pattern end-to-end — column header, toggle, htmx target, and pagination — from a handful of parameters. See components/datatable.
#Related
- Paginated list — sort state should be preserved across page changes.
- Debounced live search — combine search + sort in a single form.