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-current class.
  • Always include aria-label="Previous page" / "Next page" on the prev/next links. The label survives .is-condensed when 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"> (no href), 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

TokenDefaultDescription
--pagination-gapvar(--gap-tight)Gap between page number items
--pagination-item-size2.25remMinimum inline and block size for each page item
--pagination-radiusvar(--radius-sm)Corner radius for page items and directional links
--pagination-font-sizevar(--ui-text-sm)Font size for page numbers and labels
--pagination-item-bg-hovervar(--color-fill)Hover background for inactive page items
--pagination-item-active-bgvar(--color-state-bg)Background color for the current page indicator
--pagination-item-active-colorvar(--color-primary)Text color for the current page indicator

Slots

SlotDescription
.pagination-listOrdered list of page number items
.pagination-itemIndividual page number link or placeholder
.pagination-prev, .pagination-nextDirectional navigation link for previous or next page

Classes

ClassDescription
.paginationcomponent root
.is-ellipsisNon-interactive ellipsis placeholder between page ranges
.is-disabledDisabled state for directional links at list boundaries
.is-compactDenser pagination with smaller items and tighter gap
.is-condensedCollapses prev/next text labels at narrow container widths