Pagination
Pagination splits a long list into numbered pages and moves between them. You decide which page numbers to show; the component draws them.
<tk-pagination label="Search results"> <tk-pagination-prev href="?page=3"></tk-pagination-prev> <tk-pagination-item href="?page=1">1</tk-pagination-item> <tk-pagination-ellipsis></tk-pagination-ellipsis> <tk-pagination-item href="?page=3">3</tk-pagination-item> <tk-pagination-item href="?page=4" current>4</tk-pagination-item> <tk-pagination-item href="?page=5">5</tk-pagination-item> <tk-pagination-ellipsis></tk-pagination-ellipsis> <tk-pagination-item href="?page=20">20</tk-pagination-item> <tk-pagination-next href="?page=5"></tk-pagination-next> </tk-pagination>
Anatomy
| Part | What it is |
|---|---|
tk-pagination |
a navigation landmark holding a list of the parts below |
tk-pagination-item |
one page number: a link or a button; current marks the active page |
tk-pagination-prev / tk-pagination-next |
a chevron to the previous or next page |
tk-pagination-ellipsis |
"…" for skipped pages; not interactive |
Every part is one cell, 40px by default. Pages and prev/next are one Tab stop each.
Pages
Each page and each prev/next control is a link or a button:
- With
href, it's a link. Use it when each page has its own URL. - Without
href, it's a button that firestk-page-select. Use it when the page changes in place, such as rows in a table.
<tk-pagination label="Table rows"> <tk-pagination-prev value="1"></tk-pagination-prev> <tk-pagination-item value="1">1</tk-pagination-item> <tk-pagination-item value="2" current>2</tk-pagination-item> <tk-pagination-item value="3">3</tk-pagination-item> <tk-pagination-next value="3"></tk-pagination-next> </tk-pagination>
pagination.addEventListener('tk-page-select', (e) => showPage(e.detail.value));current marks the page on screen. It stays clickable; don't disable it.
Previous and next
Prev and next show a chevron and are named "Previous page" and "Next page". Set disabled
on prev at the first page and on next at the last.
<tk-pagination label="Search results"> <tk-pagination-prev href="?page=2">Previous</tk-pagination-prev> <tk-pagination-item href="?page=2">2</tk-pagination-item> <tk-pagination-item href="?page=3" current>3</tk-pagination-item> <tk-pagination-next disabled>Next</tk-pagination-next> </tk-pagination>
Text inside prev or next shows next to the chevron. The accessible name stays "Previous page" or "Next page", so keep the visible word inside it.
Which pages to show
The component never decides which numbers or ellipses appear. A common pattern shows the first page, the last, and the current page with one neighbor on each side, with an ellipsis for each gap:
function pageWindow(current, total, siblings = 1) {
const range = (s, e) => Array.from({ length: e - s + 1 }, (_, i) => s + i);
if (total <= siblings * 2 + 5) return range(1, total);
const left = Math.max(current - siblings, 2);
const right = Math.min(current + siblings, total - 1);
return [
1,
...(left > 2 ? ['…'] : []),
...range(left, right),
...(right < total - 1 ? ['…'] : []),
total,
]; // pageWindow(4, 20) → [1, '…', 3, 4, 5, '…', 20]
}Render each number as a tk-pagination-item, with current on the active one, and each
'…' as a tk-pagination-ellipsis.
Sizes
There is one size. To make the row more compact, set --tk-pagination-cell-size on
tk-pagination; keep it at 24px or more.
<tk-pagination label="Comments" style="--tk-pagination-cell-size: 32px"> <tk-pagination-prev disabled></tk-pagination-prev> <tk-pagination-item value="1" current>1</tk-pagination-item> <tk-pagination-item value="2">2</tk-pagination-item> <tk-pagination-next value="2"></tk-pagination-next> </tk-pagination>
A cell is square and grows for longer numbers. Numbers use tabular digits, so they line up. The row wraps when it runs out of room.
When to use
- Use pagination when the number of pages is known and jumping to one helps, such as search results, a table of records, or a gallery.
- Use a "Load more"
Button or infinite scroll for an endless feed. - Use a stepper for a linear flow such as a checkout.
- Put pagination under a
Table to page its rows. The table has no paging of its own.
Content
- Leave the page names as they are. Each page is read as "Page 4", and the ellipsis as "More pages".
- Change the names with
labelonly to translate them. - Give each pagination a
labelthat names what it pages, such as "Search results".
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
Use href when each page has a URL |
Use buttons when pages could be shared as links |
| Work out the page window in your code | Show every page number of a long list |
Mark the active page with current |
Show the active page only with a different color |
| Disable prev on the first page | Leave a prev control that goes nowhere |
| Move focus to the new results | Leave focus on a control that just became disabled |
Related
Table : pagination usually sits under it.Breadcrumb : the trail up a hierarchy, not a sequence of pages.
API
tk-pagination
| Attribute | Type | Default | What it does |
|---|---|---|---|
label |
string | Pagination |
the landmark's accessible name |
| Slot | Content |
|---|---|
| default | the parts in reading order: prev, pages and ellipses, next |
tk-pagination-item
| Attribute | Type | Default | What it does |
|---|---|---|---|
href |
string | — | makes the page a link; without it, a button that fires tk-page-select |
value |
string | the page text | the value sent with tk-page-select |
current |
boolean | false |
the active page: aria-current="page" and the selected look |
disabled |
boolean | false |
a disabled button; rarely needed |
label |
string | "Page" + the text | replaces the accessible name |
| Slot | Content |
|---|---|
| default | the page number |
tk-pagination-prev / tk-pagination-next
| Attribute | Type | Default | What it does |
|---|---|---|---|
href |
string | — | makes the control a link; without it, a button that fires tk-page-select |
value |
string | — | the target page sent with tk-page-select |
disabled |
boolean | false |
at the first or last page |
label |
string | Previous page / Next page |
replaces the accessible name |
| Slot | Content |
|---|---|
| default | optional text after the chevron |
tk-pagination-ellipsis
| Attribute | Type | Default | What it does |
|---|---|---|---|
label |
string | More pages |
what screen readers hear |
Events
| Event | Detail | When |
|---|---|---|
tk-page-select |
{ value } |
a page or prev/next without href is activated; bubbles, so listen on tk-pagination |
Links navigate and fire no event.
CSS
tk-pagination::part(list): the<ul>.--tk-pagination-cell-size: the cell's height and minimum width;40pxby default.--tk-pagination-gap: the space between cells;space-4by default.
Tokens
| Part | Tokens |
|---|---|
| cell | text/default, Label Medium (14/20), radius-8, space-8 padding-inline |
| hover / press | state/hover / state/press |
| current page | state/on + border/selected; state/on-hover under the pointer |
| disabled | text/disabled |
| ellipsis | text/secondary |
| focus ring | border/accent/focus |
state/* tokens are translucent tints, so pagination works on any surface. Values per
theme:
Accessibility
Built in:
- A
<nav>landmark named "Pagination" around a real list. - The active page has
aria-current="page". - Pages are named "Page 4", not "4", so they make sense in a screen reader's list of links.
- Prev and next are named, and their chevrons are hidden from screen readers. Disabled, they leave the Tab order and keep their names.
- The ellipsis says "More pages", so skipped pages are announced.
- Every cell has a visible focus ring and is 40px by default.
Your part:
- Work out which pages to show, and disable prev and next at the ends.
- Connect
hrefortk-page-selectto the page change. - After the page changes in place, move focus to the new content, such as the results heading.
- With more than one pagination, or another
<nav>, give each a differentlabel.
| Key | Action |
|---|---|
Tab |
moves through the cells; skips disabled ones |
Enter |
follows a link or activates a button |
Space |
activates a button |
Contrast limits
- Current page: in light and dark, its fill matches a hovered page, and in dark it has almost no fill. Its border is what marks it; don't remove it when you restyle.