# Pagination A page navigation row with Prev/Next links and numbered page buttons. The current page is highlighted. Links are built from a URL pattern. --- ## HTML structure ``` nav[aria-label=Pagination].flex.items-center.justify-center.gap-1 a.pag-btn[href=prevUrl, aria-label=Previous] ← disabled styling when current=1 svg (chevron-left) a.pag-btn[href=url] ← one per page in the visible window {pageNumber} ← current page has pag-btn-active class span.pag-ellipsis ← rendered when pages are skipped a.pag-btn[href=nextUrl, aria-label=Next] ← disabled styling when current=total svg (chevron-right) ``` --- ## CSS mechanics | Class | Effect | |---|---| | `pag-btn` | `inline-flex h-9 w-9 items-center justify-center rounded-md border border-input bg-background text-sm hover:bg-accent` | | `pag-btn-active` | `bg-primary text-primary-foreground border-primary hover:bg-primary/90` | | `pag-ellipsis` | `inline-flex h-9 w-9 items-center justify-center text-sm text-muted-foreground` | | `pointer-events-none opacity-50` | Applied to Prev when `current == 1`, to Next when `current == total` | The visible page window is limited to 7 buttons maximum. For large page counts the component collapses interior pages into ellipsis spans, keeping first page, last page, and the pages immediately around `current` always visible. --- ## Constructor signature ```csharp public Pagination( int current, int total, string urlPattern) ``` | Parameter | Description | |---|---| | `current` | 1-based current page number | | `total` | Total number of pages | | `urlPattern` | URL template with `{0}` replaced by the page number, e.g. `"/items?page={0}"` | --- ## Usage examples ### Basic pagination ```csharp new Pagination(current: 3, total: 10, urlPattern: "/blog?page={0}") ``` ### Preserving query parameters ```csharp // Build the URL pattern from the current request var query = HttpUtility.ParseQueryString(Request.QueryString.ToString()); query["page"] = "{0}"; var pattern = "/search?" + query.ToString(); new Pagination(current: page, total: totalPages, urlPattern: pattern) ``` ### HTMX-powered pagination (swap content without full navigation) The links are standard `` tags. To intercept them with HTMX, use `hx-boost` on the container or wrap in a boosted `
`: ```html
$$Pagination$$
``` ### Single page (hides naturally) ```csharp // When total == 1, Prev and Next are both disabled and only "1" is rendered. new Pagination(current: 1, total: 1, urlPattern: "/items?page={0}") ``` --- ## Tips and tricks - The `urlPattern` uses `string.Format`-style `{0}` — do not use `{page}` or other named placeholders. - Page numbers are 1-based throughout — the first page is page `1`. - When `total` is 0 or negative the component renders nothing — guard `total > 1` in the page if you want to hide it entirely when there is only one page. - To preserve sort order or filters across pages, include those values in the `urlPattern` query string. - For very small page counts (2–3 pages), all page buttons are shown with no ellipsis. - For very small page counts (2–3 pages), all page buttons are shown with no ellipsis. --- ## Complete page example **`Templates/BlogPage.htmx`** ```html

Blog

$$PostList$$
$$Pagination$$
``` **`Templates/BlogPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class BlogPage : BlogPageBase { private readonly byte[] _postList; private readonly IHtmxComponent _pagination; public BlogPage(IEnumerable posts, int currentPage, int totalPages) { var sb = new System.Text.StringBuilder(); foreach (var post in posts) { sb.Append($"""

{System.Net.WebUtility.HtmlEncode(post.Title)}

{post.PublishedAt:MMMM d, yyyy}

{System.Net.WebUtility.HtmlEncode(post.Summary)}

"""); } _postList = sb.ToString().ToUtf8Bytes(); _pagination = new Components.Pagination( current: currentPage, total: totalPages, urlPattern: "/blog?page={0}"); } protected override void RenderPostList(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_postList); protected override void RenderPagination(HtmxRenderContext ctx) => _pagination.Render(ctx.Next()); } ``` **GET handler** ```csharp [Handler] [MapGet("/blog")] public static partial class GetBlogHandler { public record Query([property: FromQuery] int Page = 1); private static async Task HandleAsync( Query q, HttpContext ctx, BlogService blog, CancellationToken ct) { const int pageSize = 10; var (posts, total) = await blog.GetPageAsync(q.Page, pageSize, ct); int totalPages = (int)Math.Ceiling(total / (double)pageSize); return await ctx.WriteHtmxPage( new BlogPage(posts, q.Page, totalPages), title: "Blog"); } } ```