# Pagination
A row of numbered page links — Previous, 1, 2, 3…, Next. Use it at the bottom of a list or table when there are too many items to show all at once. You give it the current page number, the total number of pages, and a URL pattern; it builds all the links automatically.
---
## Quick example
```csharp
new Pagination(current: 3, total: 10, urlPattern: "/blog?page={0}")
```
This renders a navigation row with links to pages 1–10 (with ellipsis for interior pages) and the Previous/Next arrows.
---
## All the options
```csharp
public Pagination(
int current,
int total,
string urlPattern)
```
| Parameter | What it does |
|---|---|
| `current` | The currently active page. 1-based (the first page is `1`). |
| `total` | The total number of pages. |
| `urlPattern` | A URL with `{0}` where the page number goes. E.g. `"/items?page={0}"`. |
The visible page window is at most 7 buttons. For large page counts, interior pages collapse into ellipsis (`…`) while the first page, last page, and pages close to `current` stay visible.
---
## Real-world examples
### Basic list with pagination
```html
$$Posts$$
$$Pager$$
```
```csharp
// Templates/BlogPage.htmx.cs
_pager = new Pagination(
current: page,
total: totalPages,
urlPattern: "/blog?page={0}");
```
### Preserving filters and sort order across pages
Build the URL pattern to include any query parameters that should survive page navigation:
```csharp
var urlPattern = $"/users?role={role}&sort={sort}&page={{0}}";
new Pagination(current: page, total: totalPages, urlPattern: urlPattern)
```
> Note the double braces `{{0}}` to produce a literal `{0}` after string interpolation.
### HTMX-powered pagination (no full page reload)
Wrap the pagination (and the content it controls) in a `hx-boost` container:
```html
```
---
## How it works
All links are plain `` elements — no JavaScript required. The URL for each page is built by calling `string.Format(urlPattern, pageNumber)`. When `current == 1`, the Previous link is styled as disabled (pointer-events removed, opacity reduced); same for Next when `current == total`.
---
## 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($"""
{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");
}
}
```