Moves between the pages of a long list: numbered buttons in a bar, Previous and Next around a select of pages, or a range readout for a table.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
typesmbk-pagination--type-<value> | numbersselectrange | numbers | Figma: Default (numbers), Select, Range. |
borderlesssmbk-pagination--borderless | On or off | Off | The numbers bar without its border. Figma: Border=No. |
Examples
Usage
Numbered pages in a bar, the current one held pressed, its number bolder.
In the middle
Far from both ends, the numbers collapse to gaps on both sides.
Without border
The same bar without its border, for a table's foot.
Few pages
With few pages every number shows.
Select
Previous and Next around a select of pages, for long lists.
Range
Which rows are showing out of how many, with a small select of ranges, for a table's foot.
Guidelines
Purpose
Moves the reader between the pages of a list too long to show at once, and tells them where they are in it. The numbers type lets them jump anywhere; the select type is for long lists where a row of numbers would not fit.
When to use
- Search results, tables and archives split into pages.
When not to use
- Feeds that grow as the reader scrolls. Use a "load more" button.
- Steps in a form. Use a progress indicator and Next buttons.
- Fewer than two pages. Show nothing.
Usage rules
Do
- Reflect the page in the address, so a reload or a shared link lands on the same page.
- Give each page's address when the pages are built ahead of time or should be found by a search engine.
Don't
- Show a numbers bar for hundreds of pages; use the select type.
- Use the range type outside a table.
Use the select type for hundreds of pages, so the reader can jump straight to any one.
A numbers bar for hundreds of pages offers only the first few and the last, and nothing in between.
Anatomy
- The block, a navigation landmark.
- Numbers type: the list part, a bordered bar holding the previous part, one page part per shown page, a gap part where pages are skipped, and the next part. Beside them the status part, "6 of 11", shown in place of the numbers in a narrow space; markup written by hand includes it, as an item of the list right after the previous part. Each page part carries its number in
data-labelas well, so a number turning bolder moves nothing beside it. - Select type: the previous part, the select part listing every page, and the next part, with no bar. Previous and Next are each there twice, with their words (the wide-only part) and as icons alone (the narrow-only part); the layout switch shows one of each.
- Range type: a label part saying "Showing", the select part listing the row ranges, and a label part with the total, for a table's foot.
- Given each page's address, the page, previous and next parts are links drawn as the same buttons, not buttons. Previous on the first page and Next on the last are then links with no address.
Behavior
The numbers bar can drop its border, which the table's foot does. The range type's select is the small one unless selectSize says otherwise, and as wide as its value, so every control in the foot is the same height. The current page is held in the pressed look, its number one weight bolder, and cannot be pressed again. Page numbers use digits of equal width, so they line up and do not shift as they change. A page button is a square up to two digits and grows with a longer number, so a page past 999 fits. In a narrow space, where a row of numbers would run off the screen, the numbers bar shows its short form instead: Previous, the page out of how many ("6 of 11"), and Next; the select type's Previous and Next show their icons alone. The form follows the layout switch, so a wrapper can force either. Previous is disabled on the first page and Next on the last; they stay in the tab order (aria-disabled), so pressing Next onto the last page keeps focus on it rather than dropping it to the top of the page. With many pages the numbers collapse to the first, the last, and a few around the current one, with a gap between; how many around the current one is a prop.
Numbers are written the way the page's language writes them: 1,116 in English, and in another language given locale. That holds for the page numbers and their names, the counts and the range's choices. Words the page gives itself, such as countText, receive the bare numbers, to write as they please.
The pages can be buttons or links. As buttons, a press calls onChange with the page and the page changes the list in place. Given each page's address (href, a function from a page's number to its address), every page and Previous and Next are links to their pages instead, which work with no script at all. They look the same, the current page included. Previous on the first page and Next on the last are links with no address, dimmed and out of the Tab order: a press loads another page anyway, so there is no focus to keep. In the select and range types, choosing in the select goes to that page's address, which needs the page's script.
Give addresses when each page has an address of its own: pages built ahead of time as files, and pages a search engine should follow. Keep onChange when the list changes in place, such as a table filtered and sorted on the page.
Accessibility
What it does for you
- The landmark is named "Pagination" by default.
- The current page carries the current-page marker, and every number button is named "Page N" so the announcement is not just a digit. It differs from the others by its weight as well as its fill, so it is found without telling colors apart.
- In the short form the page out of how many is plain text between the two buttons, read in that order.
- The arrow buttons are icon-only and carry their names.
- In the select type the select is named "Page". Its Previous and Next keep their names when they show their icons alone ("Previous page", "Next page"); the form not shown is hidden from everyone, so each is read once.
- Previous and Next stay in the tab order when disabled, so pressing Next onto the last page keeps focus on it. In the links form they are left out of it instead, since every press loads a new page.
- In the links form, the current page's link carries the current-page marker, and Previous and Next say which way they lead to a search engine as well (
relof prev and next). - Every word it says by itself, such as the buttons' names, the select's name and the counts, keeps its English unless the page gives its own, so a page in another language can change it.
- In a forced-color mode, the current page is drawn in the system's highlight colors.
What you must do
- Pass another name for the landmark when a page has two.
- Give the words in the page's own language when the page is not in English, and the language itself (
locale), so its numbers follow.
How to test
- Tab through it: every number button, Previous and Next is a stop with the focus ring showing. On the last page, press Next: focus stays on it.
- Press Enter or Space on a number: that page opens, and it is held in the pressed look.
- With a screen reader, it is a navigation landmark named "Pagination", each number is read as "Page N", and the current one as current.
- With text at 200% and the page 320 pixels wide, the numbers bar shows its short form, "6 of 11", with Previous and Next.
- With thousands of pages, on the last one: every number stays inside its button.
- In a forced-color mode, the current page is still told from the others.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the next button, link or the select. Previous and Next stay in the order when disabled, except in the links form. |
| EnterorSpace | Presses the focused button: opens that page, or goes to the previous or next one. In the links form, Enter follows the focused link. |
Source
The files the pagination is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-pagination — Pagination. Figma "Pagination" 4253:3044 (Type: Default, Select, Range; Border: Yes, No).
* The numbers type is a bar of ghost buttons, the current page held in the
* pressed look, with or without its border. The select type is Previous, a
* select of pages, Next. The range type says which rows are showing out of
* how many, with a select of ranges, for a table's foot.
* The current page shows by its pressed fill and a label one weight bolder, so
* it is found without telling colors apart. Digits are tabular, every one as
* wide as the next, so numbers line up and do not shift as they change.
* In a narrow space the numbers bar has no room for its numbers, so it shows
* a short form: Previous, "6 of 11", Next (the status part); the select type's
* Previous and Next show their icons alone. */
.smbk-pagination {
display: inline-flex;
align-items: center;
}
.smbk-pagination__list {
box-sizing: border-box;
display: inline-flex;
align-items: center;
gap: var(--smbk-space-inline-xs);
margin: 0;
padding: var(--smbk-space-inset-xs);
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
border-radius: var(--smbk-radius-control-lg);
list-style: none;
}
.smbk-pagination--borderless .smbk-pagination__list {
border-color: transparent;
}
.smbk-pagination__item {
display: inline-flex;
}
/* At least a square, and as wide as its number needs with the inset beside it, so a page past 999 fits. Column, so the
copy below lies under the number and not beside it. */
.smbk-pagination__page {
flex-direction: column;
gap: 0;
min-width: var(--smbk-size-control-sm);
padding: 0 var(--smbk-space-inset-xs);
font-variant-numeric: tabular-nums;
}
/* The button is always as wide as its number at the current page's weight: an invisible copy of the number
(`data-label`, which the wrapper writes) lies under it with no height, so a page turning current never moves the
pages beside it. The copy is generated content, hidden, with empty words for assistive technology. */
.smbk-pagination__page[data-label]::after {
content: attr(data-label);
content: attr(data-label) / "";
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
font-weight: var(--smbk-typography-body-sm-semibold-font-weight);
pointer-events: none;
user-select: none;
}
/* Figma holds the current page in the pressed fill; its label one weight bolder, the semibold of the
same 14 px, is the code's own: the weight variants of the text styles live in the token source, not
in Figma. Only the weight changes, so the digit sits level with the others. */
.smbk-pagination__page[aria-current="page"] {
background: var(--smbk-color-action-ghost-background-pressed);
font-weight: var(--smbk-typography-body-sm-semibold-font-weight);
}
.smbk-pagination__gap {
display: inline-block;
text-align: center;
min-width: var(--smbk-size-control-sm);
color: var(--smbk-color-text-subtlest);
font-family: var(--smbk-typography-body-sm-font-family);
font-size: var(--smbk-typography-body-sm-font-size);
font-weight: var(--smbk-typography-body-sm-font-weight);
line-height: var(--smbk-typography-body-sm-line-height);
letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}
/* The short form's "6 of 11", shown only in a narrow space. */
.smbk-pagination__status {
display: none;
align-items: center;
padding: 0 var(--smbk-space-inset-md);
color: var(--smbk-color-text-default);
font-family: var(--smbk-typography-body-sm-font-family);
font-size: var(--smbk-typography-body-sm-font-size);
font-weight: var(--smbk-typography-body-sm-font-weight);
line-height: var(--smbk-typography-body-sm-line-height);
letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* The select type's Previous and Next: with their words in a wide space, their icons alone in a
narrow one, so the three fit a phone. */
.smbk-pagination .smbk-pagination__narrow-only {
display: none;
}
@container style(--smbk-layout: narrow) {
.smbk-pagination__item:has(> .smbk-pagination__page, > .smbk-pagination__gap) {
display: none;
}
.smbk-pagination__status,
.smbk-pagination .smbk-pagination__narrow-only {
display: inline-flex;
}
.smbk-pagination .smbk-pagination__wide-only {
display: none;
}
}
/* ---- type: select ---- */
.smbk-pagination--type-select {
gap: var(--smbk-space-inline-lg);
}
.smbk-pagination--type-select .smbk-pagination__select {
width: auto;
}
.smbk-pagination__select .smbk-select__value,
.smbk-pagination__select .smbk-select__option-label {
font-variant-numeric: tabular-nums;
}
/* ---- type: range ---- */
.smbk-pagination--type-range {
gap: var(--smbk-space-inline-lg);
}
.smbk-pagination--type-range .smbk-pagination__select {
width: auto;
}
/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
user's own colors. Nothing here applies outside that mode. */
@media (forced-colors: active) {
.smbk-pagination__page[aria-current="page"] {
forced-color-adjust: none;
border-color: Highlight;
background: Highlight;
color: HighlightText;
box-shadow: none;
}
.smbk-pagination__page[aria-current="page"]:focus-visible {
outline-color: CanvasText;
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.inline.xs--smbk-space-inline-xs | 4px | Spacing | |
| space.inset.xs--smbk-space-inset-xs | 4px | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| radius.control.lg--smbk-radius-control-lg | 8px | Radius | |
| size.control.sm--smbk-size-control-sm | comfortable: 32pxcompact: 28pxchanges with density | Size | |
| typography.body-sm-semibold--smbk-typography-body-sm-semibold-font-family--smbk-typography-body-sm-semibold-font-size--smbk-typography-body-sm-semibold-font-weight--smbk-typography-body-sm-semibold-line-height--smbk-typography-body-sm-semibold-letter-spacing--smbk-typography-body-sm-semibold-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 600lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.action.ghost.background-pressed--smbk-color-action-ghost-background-pressed | light: #c4cdd8dark: #2d3237 | Color | |
| color.text.subtlest--smbk-color-text-subtlest | light: #606a75dark: #7c8692changes with theme | Color | |
| typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| space.inset.md--smbk-space-inset-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| space.inline.lg--smbk-space-inline-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing |
Manifest
{
"name": "pagination",
"block": "smbk-pagination",
"summary": "Moves between the pages of a long list: numbered buttons in a bar, Previous and Next around a select of pages, or a range readout for a table.",
"adapt": "css",
"axes": {
"type": {
"values": [
"numbers",
"select",
"range"
],
"default": "numbers",
"description": "Figma: Default (numbers), Select, Range."
}
},
"flags": {
"borderless": {
"description": "The numbers bar without its border. Figma: Border=No."
}
},
"parts": [
"list",
"item",
"page",
"gap",
"status",
"previous",
"next",
"select",
"label",
"wide-only",
"narrow-only"
],
"figmaNodeId": "4253:3044"
}