N

Paginated list

Prev/next plus numbered pages, rendered from a single GET. Htmx swaps the table in place — no full navigation.

#When to use

Classic catalog/table paging. For "load more"-style experiences, see Infinite scroll.

#Markup

html
<div id="products_table">
    <partial name="_ProductsTable" model="Model" />
    <nav>
        @for (int p = 1; p <= totalPages; p++)
        {
            <a hx-get="/products?page=@p"
               hx-target="#products_table"
               hx-swap="outerHTML"
               hx-push-url="true"
               href="/products?page=@p"
               class="@(p == currentPage ? "active" : "")">@p</a>
        }
    </nav>
</div>
  • hx-push-url="true" keeps the address bar honest, so refresh / share / back-button all land on the right page.
  • The href is preserved as a real URL — users without JS still get a working page-link.

#Handler

csharp
public async Task<IActionResult> OnGetAsync(int page = 1, int pageSize = 20)
{
    var results = await _products.ListPageAsync(page, pageSize);
    return this.PageOrPartial("_ProductsTable", results);
}

PageOrPartial returns the full page on a normal navigation and the _ProductsTable partial when htmx requests it (detected via the HX-Request header).

#Preserve other query state

When pagination coexists with sort / search, every link needs to carry the full state:

cshtml
<a hx-get="/products?page=@p&sort=@sort&dir=@dir&q=@q"
   hx-target="#products_table"
   hx-swap="outerHTML"
   hx-push-url="true"
   href="/products?page=@p&sort=@sort&dir=@dir&q=@q">@p</a>

The Pagination component in ndui add pagination handles this automatically when you bind it to the current IDictionary of query params.