Pagination
tier 2 — classesMoving through a long result set, and saying where the current page sits.
<a href> for every page that
exists, not <button> — a page of results should be linkable, openable in a
new tab and reachable with the back button. Only the disabled ends are buttons or spans.
Pagination
Wrap it in <nav aria-label="Pagination"> and put
aria-current="page" on the current one. The class colours it;
aria-current is what is announced. .page-count pushes the range
to the far end, which is where the reader looks to find out how much is left.
<nav class="pagination" aria-label="Pagination">
<span class="page-link" aria-disabled="true"><i class="ri-arrow-left-s-line"></i> Previous</span>
<a class="page-link" href="#" aria-label="Page 1">1</a>
<a class="page-link" href="#" aria-current="page" aria-label="Page 2">2</a>
<a class="page-link" href="#" aria-label="Page 3">3</a>
<span class="page-gap" aria-hidden="true">…</span>
<a class="page-link" href="#" aria-label="Page 61">61</a>
<a class="page-link" href="#">Next <i class="ri-arrow-right-s-line"></i></a>
<span class="page-count">21–40 of 1,204</span>
</nav>
When the page is view state
Where the page number is view state rather than a location — a Blazor Server table whose
page, sort and filters live in the circuit — use
<button class="page-link" type="button"> throughout, with
disabled on the ends rather than aria-disabled. Judge it by
whether the page has an address: if a reader could reasonably send someone a link to page 4,
use the anchors above.
<nav class="pagination" aria-label="Pagination">
<button class="page-link" type="button" disabled>
<i class="ri-arrow-left-s-line"></i> Previous
</button>
<button class="page-link" type="button" aria-current="page">1</button>
<button class="page-link" type="button">2</button>
<button class="page-link" type="button">3</button>
<span class="page-gap" aria-hidden="true">…</span>
<button class="page-link" type="button">61</button>
<button class="page-link" type="button">
Next <i class="ri-arrow-right-s-line"></i>
</button>
<span class="page-count">1–20 of 1 211</span>
</nav>
Which numbers to show, from C#
Live — page through it. SednaPager.Window(current, total)
returns the slots to render — page numbers and .page-gaps — so the markup stays
a foreach. The count of slots is the same on every page, so the buttons do not
shift under the pointer, and a gap never hides a single page. edge and
around widen the ends and the neighbourhood.
<nav class="pagination" aria-label="Pagination">
<button class="page-link" type="button" disabled="@(_page == 1)" @onclick="@(() => _page--)">
<i class="ri-arrow-left-s-line"></i> Previous
</button>
@foreach (var slot in SednaPager.Window(_page, Pages))
{
if (slot.IsGap)
{
<span class="page-gap" aria-hidden="true">…</span>
continue;
}
var number = slot.Number;
<button class="page-link" type="button" aria-current="@(slot.IsCurrent ? "page" : null)"
@onclick="@(() => _page = number)">@number</button>
}
<button class="page-link" type="button" disabled="@(_page == Pages)" @onclick="@(() => _page++)">
Next <i class="ri-arrow-right-s-line"></i>
</button>
<span class="page-count">Page @_page of @Pages</span>
</nav>
@code {
private const int Pages = 61;
// The current page is the app's state; SednaPager only decides which numbers to show.
private int _page = 1;
}
Breadcrumbs
For where a page sits when the sidebar cannot show it — a record three levels inside a
section; if the sidebar already highlights the page, a breadcrumb earns nothing. The last
item is the current page: mark it aria-current="page" and leave it a
<span>, not a link to where you already are.
<nav class="breadcrumb" aria-label="Breadcrumb">
<a class="breadcrumb-item" href="#"><i class="ri-home-4-line"></i> Console</a>
<a class="breadcrumb-item" href="#">Orders</a>
<a class="breadcrumb-item" href="#">EU-Central</a>
<span class="breadcrumb-item" aria-current="page">ORD-4209</span>
</nav>
Prev, next and the range
Enough for a short list, and for a pager that has to live inside a
toolbar row. .page-count is what makes it usable without
numbers, and the disabled ends are <span aria-disabled="true">, not links
to nowhere.
<div class="sedna-col sedna-gap-2">
<nav class="pagination" aria-label="Order pages">
<a class="page-link" href="#"><i class="ri-arrow-left-s-line"></i> Previous</a>
<span class="page-count">1,181–1,204 of 1,204</span>
<span class="page-link" aria-disabled="true">Next <i class="ri-arrow-right-s-line"></i></span>
</nav>
<div class="toolbar">
<div class="toolbar-filters">
<label class="form-check"><input type="checkbox" /> Only mine</label>
</div>
<nav class="pagination" aria-label="Order pages">
<span class="page-link" aria-disabled="true"><i class="ri-arrow-left-s-line"></i></span>
<a class="page-link" href="#" aria-current="page" aria-label="Page 1">1</a>
<a class="page-link" href="#" aria-label="Page 2">2</a>
<a class="page-link" href="#" aria-label="Page 3">3</a>
<a class="page-link" href="#" aria-label="Next page"><i class="ri-arrow-right-s-line"></i></a>
</nav>
</div>
</div>
Above the page head
Where a breadcrumb belongs: above the heading, so the trail is read before the title it leads
to, with .page-head carrying the title, the supporting
line and the actions that apply to the whole page. One <h1> per page and
it is this one — the breadcrumb is navigation, not a heading level.
ORD-4209
Five lines, three reserved. Raised 31 July at 09:14.
<div>
<nav class="breadcrumb" aria-label="Breadcrumb">
<a class="breadcrumb-item" href="#"><i class="ri-home-4-line"></i> Console</a>
<a class="breadcrumb-item" href="#">Orders</a>
<a class="breadcrumb-item" href="#">EU-Central</a>
<span class="breadcrumb-item" aria-current="page">ORD-4209</span>
</nav>
<div class="page-head">
<div class="page-head-text">
<h1>ORD-4209</h1>
<p class="lede">Five lines, three reserved. Raised 31 July at 09:14.</p>
</div>
<div class="page-head-actions">
<button class="btn" type="button"><i class="ri-printer-line"></i> Print</button>
<button class="btn btn-go" type="button"><i class="ri-send-plane-line"></i> Dispatch</button>
</div>
</div>
</div>