Page navigation is the pager at the foot of a page: previous on the left, next on the right. It works across any ordered sequence and knows nothing about what the sequence is — pair it with Book for a documentation space, or with anything else that has an order. The class is required, and data-direction chooses the side.
It is the sideways move in a three-part model. Down is Book — shelf to cover to contents. Up is Breadcrumb. Sideways is this.
Anatomy
nav.page-nav
.page-nav-inner
a.page-nav-link data-direction="prev"
.svg-icn.page-nav-arrow
.page-nav-text
.page-nav-label ← "Previous" / "Next"
.page-nav-section ← only when the neighbour is in another section
.page-nav-title ← the target page's name
a.page-nav-link data-direction="next"
…
Both directions render from identical markup — the arrow always comes first in the DOM. data-direction="next" reverses the row in CSS, so this is one component with a variant rather than two that happen to share class names.
The title is a <span>, not a heading. It labels a link; it does not open a section. Two <h3>s in the page footer would put phantom entries in the document outline that screen-reader users navigate by.
Basic usage
Both halves filled — the reader is mid-sequence.
Note the arrow is written first inside both links. The next link only looks reversed — data-direction="next" flips the row in CSS. Narrow the window and watch the two halves stack; the chevron stays on the right where it belongs.
<nav class="page-nav" aria-label="Page navigation">
<div class="page-nav-inner">
<a href="/brand/iconography.html" class="page-nav-link" data-direction="prev" rel="prev">
<div class="svg-icn page-nav-arrow"><!-- chevron-left-large --></div>
<span class="page-nav-text">
<span class="page-nav-label">Previous</span>
<span class="page-nav-title">4.4 Iconography</span>
</span>
</a>
<a href="/brand/tone-of-voice.html" class="page-nav-link" data-direction="next" rel="next">
<div class="svg-icn page-nav-arrow"><!-- chevron-right-large --></div>
<span class="page-nav-text">
<span class="page-nav-label">Next</span>
<span class="page-nav-title">4.6 Motion</span>
</span>
</a>
</div>
</nav>
Note the arrow comes first in both links. Do not reorder it for the next direction — the CSS handles that, and moving it breaks the shared shape.
rel="prev" and rel="next" describe the sequence to browsers and crawlers. Keep them.
The section line
appears only when the neighbour sits in a different section. It marks the moment you leave one book for the next, which is the point at which the destination needs naming.
Showing it on every link would turn a signpost into chrome — on a page whose neighbour is in the same section, repeating that section's name tells the reader nothing they don't already know.
Here the reader is on the last page of the Brand Book. Previous stays inside the book and says nothing extra; next names where it is about to take them.
<span class="page-nav-text">
<span class="page-nav-label">Next</span>
<span class="page-nav-section">Design System</span>
<span class="page-nav-title">Introduction</span>
</span>
Only render the element when it has content. An empty <span class="page-nav-section"></span> still draws a gap inside the column, because is a flex container with a row gap.
Ends of the sequence
When there is no neighbour in a direction, render nothing for that side. Do not emit a hidden placeholder: grid-column pins each link to its own half, so the surviving link keeps its side on its own.
First page in the sequence — next only, still sitting on the right:
Last page — previous only, on the left:
<!-- first page in the sequence: next only, still on the right -->
<nav class="page-nav" aria-label="Page navigation">
<div class="page-nav-inner">
<a href="…" class="page-nav-link" data-direction="next" rel="next">…</a>
</div>
</nav>
If both neighbours are missing, render no <nav> at all rather than an empty one.
Responsive
Below 768px the two halves collapse into a single column and stack — previous above next — with a rule between them. The breakpoint belongs to the component rather than a shared responsive section, so it travels into another product with the rest of the CSS.
The title also steps down a size below 960px, matching the heading scale. That step is carried by --page-nav-title-size, so re-pointing the token changes both sizes from one place.
Accessibility
- Label the landmark.
<nav>needsaria-label="Page navigation", so a screen-reader user moving by landmark can tell it apart from the site's main navigation. - The title is a
<span>, deliberately. Using a heading here injects entries into the document outline at the foot of every page, which is misleading when navigating by heading. - The arrow is decorative and must carry
aria-hidden="true". It repeats the direction the label already states. Icons rendered through the wrapper already do this. - The accessible name is the whole link text — "Next", the section if present, and the title. That reads well and satisfies SC 2.4.4 Link Purpose. Do not add an
aria-labelthat drops the title. rel="prev"/rel="next"describe the sequence programmatically. They are not a substitute for the visible label.- Reversing the row does not affect the tab order. Each link is a single tab stop, and the reversal is visual only.
Usage rules
Do:
- Keep the arrow first in the DOM for both directions and let
data-directiondo the flipping - Render the section line only when the neighbour is in a different section, and only when it has content
- Omit the whole link when there is no neighbour in that direction
- Re-point tokens to restyle — a product changes
--page-nav-link-padding, not the rule
Don't:
- Don't use a heading element for
- Don't emit a hidden placeholder to hold an empty column;
grid-columnalready does - Don't add or a container class to — the links carry their own inset
- Don't confuse with the sidebar's ; they are unrelated components
- Don't reach for this when the set has no meaningful order — a pager implies a sequence a reader can follow
CSS reference
This section documents how the component is built. For usage, see the sections above.
Tokens
| Token | Default | What it controls |
|---|---|---|
| Link fill | ||
| Link fill on hover | ||
var(--border-s) solid var(--border-faded) |
Top rule, and the divider between stacked links on mobile | |
var(--space-xl) var(--space-2xl) |
Inset inside each half | |
| Space between arrow and text | ||
| Title size; steps to below 960px | ||
| Arrow at rest | ||
| Arrow on hover |
Selectors
| Selector | Purpose |
|---|---|
| Landmark wrapper; declares the component tokens and the top rule | |
| Two-column grid, no gutter | |
| One direction — the whole half is the link | |
.page-nav-link[data-direction="prev"] |
Pinned to column 1 |
.page-nav-link[data-direction="next"] |
Pinned to column 2, row reversed, right aligned |
| Direction chevron; decorative | |
| Label, section, and title stacked | |
| Eyebrow naming the relationship | |
| Target's section — only when crossing | |
| Target page name, clamped to two lines |
Use in another product
The design system installs once per product:
npm install github:bydefaultstudio/design-system-dist#semver:^4.7.0
This component's styles ship in design-system.css. No JavaScript, nothing else to include.