Pagination
Part of the extras bundle, not included by default.
Intent
Page navigation for paginated lists, search results, or any
long sequence the consumer renders one chunk at a time. The
structure is a directional previous control,
an ordered list of page links, and a next
control. The current page sits in
aria-current="page"; ellipsis placeholders
(.is-ellipsis) signal "more pages skipped here"
in long sequences.
Brio doesn't try to auto-collapse the page-number list at
narrow widths -- the consumer's app already chose those
numbers based on count and current page, and CSS shouldn't
second-guess that. What the CSS does handle is
label-collapse on prev/next at narrow container widths
(.is-condensed) and an overall denser variant
for sidebars or inline-table footers (.is-compact).
Basic usage
<nav class="pagination" aria-label="Pagination">
<a class="pagination-prev" href="#" aria-label="Previous page">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
<span class="pagination-label">Previous</span>
</a>
<ol class="pagination-list">
<li><a class="pagination-item" href="#">1</a></li>
<li><a class="pagination-item" href="#">2</a></li>
<li><a class="pagination-item" href="#" aria-current="page">3</a></li>
<li><a class="pagination-item" href="#">4</a></li>
<li><a class="pagination-item" href="#">5</a></li>
</ol>
<a class="pagination-next" href="#" aria-label="Next page">
<span class="pagination-label">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</nav>With ellipsis
For long sequences the consumer renders an
.is-ellipsis placeholder between distant page
numbers. The element is non-interactive (typically a
<span>); brio paints it muted so the gap
reads as "skipped pages" rather than a clickable item.
<nav class="pagination" aria-label="Pagination">
<a class="pagination-prev" href="#" aria-label="Previous page">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
<span class="pagination-label">Previous</span>
</a>
<ol class="pagination-list">
<li><a class="pagination-item" href="#">1</a></li>
<li><span class="pagination-item is-ellipsis" aria-hidden="true">…</span></li>
<li><a class="pagination-item" href="#">4</a></li>
<li><a class="pagination-item" href="#" aria-current="page">5</a></li>
<li><a class="pagination-item" href="#">6</a></li>
<li><span class="pagination-item is-ellipsis" aria-hidden="true">…</span></li>
<li><a class="pagination-item" href="#">20</a></li>
</ol>
<a class="pagination-next" href="#" aria-label="Next page">
<span class="pagination-label">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</nav>Disabled controls
On the first page the previous control is disabled; on the
last page, the next control. Render the disabled side as a
<span> (no link target) with
.is-disabled for the dimmed visual, or a
<button disabled> if the surrounding form
expects a button. brio's CSS handles both.
<nav class="pagination" aria-label="Pagination">
<span class="pagination-prev is-disabled" aria-disabled="true">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
<span class="pagination-label">Previous</span>
</span>
<ol class="pagination-list">
<li><a class="pagination-item" href="#" aria-current="page">1</a></li>
<li><a class="pagination-item" href="#">2</a></li>
<li><a class="pagination-item" href="#">3</a></li>
</ol>
<a class="pagination-next" href="#" aria-label="Next page">
<span class="pagination-label">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</nav>Small variant
.is-compact drops item size, gap, and font down to
chrome-density tiers. Use in sidebars, inline-table footers,
or anywhere a full-size pagination would dominate its
container.
<nav class="pagination is-compact" aria-label="Pagination">
<a class="pagination-prev" href="#" aria-label="Previous page">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
<span class="pagination-label">Previous</span>
</a>
<ol class="pagination-list">
<li><a class="pagination-item" href="#">1</a></li>
<li><a class="pagination-item" href="#" aria-current="page">2</a></li>
<li><a class="pagination-item" href="#">3</a></li>
<li><a class="pagination-item" href="#">4</a></li>
</ol>
<a class="pagination-next" href="#" aria-label="Next page">
<span class="pagination-label">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</nav>Condensed (narrow widths)
.is-condensed sets
container-type: inline-size on the
<nav> and a container query collapses the
text inside .pagination-label at widths below
28rem. The icons and the surrounding link still render; the
aria-label on the link itself is the source of
truth for assistive tech, so the meaning is preserved when
the visible text disappears. Resize the demo below to see
the labels collapse.
<docs-resize>
<nav class="pagination is-condensed" aria-label="Pagination">
<a class="pagination-prev" href="#" aria-label="Previous page">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
<span class="pagination-label">Previous</span>
</a>
<ol class="pagination-list">
<li><a class="pagination-item" href="#">1</a></li>
<li><span class="pagination-item is-ellipsis" aria-hidden="true">…</span></li>
<li><a class="pagination-item" href="#">4</a></li>
<li><a class="pagination-item" href="#" aria-current="page">5</a></li>
<li><a class="pagination-item" href="#">6</a></li>
<li><span class="pagination-item is-ellipsis" aria-hidden="true">…</span></li>
<li><a class="pagination-item" href="#">20</a></li>
</ol>
<a class="pagination-next" href="#" aria-label="Next page">
<span class="pagination-label">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
</nav>
</docs-resize>
Need a different breakpoint? CSS container query lengths
can't be var()-driven (the value resolves at
rule parse time, before custom properties), so the threshold
isn't tokenized. Override the rule in your own CSS:
/* In your project CSS, after brio-extras.css */
@container (max-inline-size: 36rem) {
.pagination.is-condensed .pagination-label {
display: none;
}
}
Customization
Item size, gap, radius, and font size flow through scoped
--pagination-* tokens. Hover backgrounds use
--color-fill -- the new neutral fill scale that
lifts in dark mode and stays quiet in light. The active page
pulls --color-primary, matching brio's
"primary color is for state" rule (the current-page indicator
is selection state, signaled chromatically).
Accessibility
-
Wrap the pagination in
<nav aria-label="Pagination">so the landmark is discoverable. -
Use
<ol>for the page list -- ordered position is meaningful; assistive tech announces position. -
Mark the current page with
aria-current="page". CSS binds visual treatment to the attribute; no.is-currentclass. -
Always include
aria-label="Previous page"/"Next page"on the prev/next links. The label survives.is-condensedwhen the visible text collapses. -
Mark ellipsis placeholders
aria-hidden="true"-- assistive tech doesn't need to announce the literal "…" character; it's a visual gap-marker only. -
Disabled prev/next: render as
<span class="pagination-prev is-disabled" aria-disabled="true">(nohref), or<button disabled>inside a form flow. Both get the dimmed style; both prevent activation.
Markup
<nav class="pagination" aria-label="Pagination">
<a class="pagination-prev" href="?page=4" aria-label="Previous page">
<svg aria-hidden="true">...</svg>
<span class="pagination-label">Previous</span>
</a>
<ol class="pagination-list">
<li><a class="pagination-item" href="?page=1">1</a></li>
<li><span class="pagination-item is-ellipsis" aria-hidden="true">…</span></li>
<li><a class="pagination-item" href="?page=4">4</a></li>
<li><a class="pagination-item" href="?page=5" aria-current="page">5</a></li>
<li><a class="pagination-item" href="?page=6">6</a></li>
<li><span class="pagination-item is-ellipsis" aria-hidden="true">…</span></li>
<li><a class="pagination-item" href="?page=20">20</a></li>
</ol>
<a class="pagination-next" href="?page=6" aria-label="Next page">
<span class="pagination-label">Next</span>
<svg aria-hidden="true">...</svg>
</a>
</nav>
CSS Reference: Pagination
Page navigation for lists and search results
Source: src/css/extras/pagination.css
Tokens
| Token | Default | Description |
|---|---|---|
--pagination-gap | var(--gap-tight) | Gap between page number items |
--pagination-item-size | 2.25rem | Minimum inline and block size for each page item |
--pagination-radius | var(--radius-sm) | Corner radius for page items and directional links |
--pagination-font-size | var(--ui-text-sm) | Font size for page numbers and labels |
--pagination-item-bg-hover | var(--color-fill) | Hover background for inactive page items |
--pagination-item-active-bg | var(--color-state-bg) | Background color for the current page indicator |
--pagination-item-active-color | var(--color-primary) | Text color for the current page indicator |
Slots
| Slot | Description |
|---|---|
.pagination-list | Ordered list of page number items |
.pagination-item | Individual page number link or placeholder |
.pagination-prev, .pagination-next | Directional navigation link for previous or next page |
Classes
| Class | Description |
|---|---|
.pagination | component root |
.is-ellipsis | Non-interactive ellipsis placeholder between page ranges |
.is-disabled | Disabled state for directional links at list boundaries |
.is-compact | Denser pagination with smaller items and tighter gap |
.is-condensed | Collapses prev/next text labels at narrow container widths |