Pagination
JavaScript off: what the HTML and CSS do on their own.
Usage
foreach (var order in Orders.Skip((Page - 1) * 20).Take(20))
{
<OrderRow Order="order" />
}
<Pagination TotalItems="Orders.Count" PageSize="20" Label="Orders pages" />
{
[SupplyParameterFromQuery(Name = "page")]
private int? CurrentPage { get; set; }
private int PageCount => Math.Max(1, (Orders.Count + 19) / 20);
private int Page => Math.Clamp(CurrentPage ?? 1, 1, PageCount);
}Each page is a link that sets ?page= and keeps the rest of the address, so paging works on static pages with JavaScript off, survives a reload and can be shared. Pagination only draws the links: the page reads the number and shows its own rows. A Table pages itself with the same links.
Pages in the address
Use TotalItems and PageSize, or PageCount, for how many pages there are, and read the page with [SupplyParameterFromQuery] to show its rows.
Preview
Showing orders 121–130 of 237
Code
<Stack Gap="3" Align="Align.Start" Grow>
<TextBlock Size="TextSize.Sm" Color="Color.Muted">Showing orders (First)–(Last) of Total</TextBlock>
<Pagination TotalItems="Total" PageSize="PageSize" Label="Orders pages" />
</Stack>
{
private const int Total = 237;
private const int PageSize = 10;
// The page's links set ?page=; read it here to show the right rows.
[SupplyParameterFromQuery(Name = "page")]
private int? CurrentPage { get; set; }
private int Page => Math.Clamp(CurrentPage ?? 1, 1, (Total + PageSize - 1) / PageSize);
private int First => (Page - 1) * PageSize + 1;
private int Last => Math.Min(Page * PageSize, Total);
}Page 1 leaves the key out of the address. Pagination treats a number past the last page as the last, so clamp your own the same way, as the example does; at the ends, previous or next stays in place, marked unavailable.
More than one on a page
Use QueryKey to give each pagination on a page its own key in the address, so they page apart, and Label to name each one for screen readers.
<Pagination TotalItems="Orders.Count" QueryKey="orders-page" Label="Orders pages" />
<Pagination TotalItems="Returns.Count" QueryKey="returns-page" Label="Returns pages" />A FilterForm drops a page key, or one ending in -page, when the filters change, so filtering starts again at page 1.
Sizes and how many pages show
Use Size to match the buttons and fields around it, Siblings for the pages on each side of the current one, and Boundaries for the pages always shown at each end.
Preview
Code
<Stack Gap="4" Align="Align.Start" Grow>
<Pagination PageCount="24" Size="ControlSize.Sm" Siblings="0" QueryKey="small" Label="Small pagination" />
<Pagination PageCount="24" QueryKey="medium" Label="Medium pagination" />
<Pagination PageCount="24" Size="ControlSize.Lg" Boundaries="2" QueryKey="large" Label="Large pagination" />
</Stack>The row keeps the same number of slots as the page moves, so the controls don't jump about. Below 640px only the current page's neighbors and the ends show, so it fits a phone.
Bound to C#
Use @bind-Page on an interactive page to page in C#: the pages become buttons that update the bound value, and the address stays as it is.
Preview
C# has page 5
Code
<Stack Gap="3" Align="Align.Start" Grow>
<Pagination @bind-Page="_page" PageCount="8" QueryKey="p" Label="Results pages" />
<TextBlock Size="TextSize.Sm" Color="Color.Muted">C# has page <Code>_page</Code></TextBlock>
</Stack>
{
// Static pages have no C# to update, so the pages stay links that set ?p= (QueryKey): start from it.
[SupplyParameterFromQuery(Name = "p")]
private int? QueryPage { get; set; }
private int _page;
protected override void OnInitialized() => _page = QueryPage ?? 1;
}Off: The pages are links that set ?page= and load the page again.
A static page has no C# to update, so there the pages stay links that set the key; start the bound value from the address, as the example does with ?p=, so both work.
Its own words
Use Label for the navigation landmark's name ("Pagination"), PreviousLabel and NextLabel for the arrows ("Previous page", "Next page"), and PageLabel for the word read before each number ("Page").
Mode support
| Feature | Off | Static | Live |
|---|---|---|---|
| Page links that set ?page= and keep the rest of the address | Yes | Yes | Yes |
| Page bound to C#, with buttons in place of links | No | No | Yes |
Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).
Accessibility
- It renders a
navnamed "Pagination" (Label); give each one on a page its ownLabel. - Page numbers are read as "Page 3" (
PageLabel), and the current page hasaria-current="page". - Previous and next are named "Previous page" and "Next page" (
PreviousLabel,NextLabel); at the ends they stay, witharia-disabled. - With
@bind-Pagethey're buttons: the current page and a disabled previous or next stay buttons, so focus isn't lost when one is picked. - The gaps ("…") are hidden from screen readers.
Parameters
| Prop | Type | Default |
|---|---|---|
@bind-PageThe current page, from 1. | int? | |
PageCountHow many pages there are. | int? | |
TotalItemsHow many items there are, with PageSize, in place of PageCount. | int? | |
PageSizeItems per page, with TotalItems. | int | 20 |
QueryKeyThe query-string key for the page. | string | "page" |
SiblingsPages shown on each side of the current one. | int | 1 |
BoundariesPages always shown at each end. | int | 1 |
SizeButton size, shared with buttons and fields. | ControlSize | ControlSize.Md |
LabelThe navigation landmark's name. | string? | "Pagination" |
PreviousLabelThe previous button's name. | string? | "Previous page" |
NextLabelThe next button's name. | string? | "Next page" |
PageLabelRead before each page number by screen readers. | string? | "Page" |
Plus 16 item props and 4 common props.
Full APISee also
- Table: sort, filter, page and select, with state in the URL.
- PrevNext: links to the pages before and after.
- Breadcrumb: the path to the current page.