Components · Wayfinding
Pagination
A row of page-number and previous/next controls. The current page takes the Primary fill; prev/next disable at the bounds. Every item is at least a 32×32 target.
Preview
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Pagination" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `pagination` spec (variants: item, active, disabled) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `pagination` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
← nav aria-label="Pagination" ─────────────────────────────────────
1–25 of 340 [← Prev] [1] [2] [3] […] [14] [Next →]
↑ pagination-info ↑ active ↑ ellipsis
Show: [25 ▾]
↑ pagination-page-size
| State | Visual |
|---|---|
| Default | Page numbers, prev/next arrows |
| Current page | {colors.primary} fill, {colors.primary-text} number |
| Page hover | {colors.surface-muted} fill |
| Page focus | 2px solid {colors.border-focus} outline |
| Prev/Next disabled | {colors.disabled-text}, not-allowed cursor |
| Loading | Skeleton for page numbers |
When to use
- Tables, lists, and grids with more rows than
pageSizeallows on one screen. - Server-side paginated data where loading all records at once is impractical.
When not to use
- Lists under 25 items — show all items instead.
- Infinite scroll patterns — pagination and infinite scroll are mutually exclusive.
Do
- Show the page range info ("1–25 of 340") — users need to know the dataset size.
- Offer a page-size selector when the dataset is large and users vary in how many items they want to scan at once.
- Keep Previous/Next labels visible alongside the arrow icon — don't use icon only.
Don't
- Don't use pagination inside a modal or popover.
- Don't reset to page 1 automatically unless the user changes the search query or applies a new filter.
- "Previous" and "Next" — always spelled out, not abbreviated.
- Page range: "1–25 of 340" — use en-dash (–) not hyphen.
- Page size label: "Show: [25 ▾]" or "Rows per page: [25 ▾]".
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus through pagination controls |
Enter / Space |
Navigate to the focused page |
Left / Right Arrow |
Move focus between page items |
Accessibility
- Wrap in
<nav aria-label="Pagination">. - Active page button:
aria-current="page". - Disabled Prev/Next:
aria-disabled="true"— keep in tab order so screen readers can announce they are unavailable. - Ellipsis:
aria-hidden="true"— it is decorative, not interactive. - Each page button:
aria-label="Page 3"(not just the number).
Spec
Values and token references resolved straight from the Zetta spec.
pagination-item
- backgroundColor
- {colors.surface-card}
- textColor
- {colors.ink}
- borderColor
- {colors.border-strong}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 500
- height
- 32px
- minWidth
- 32px
- paddingX
- 12px
- display
- flex
- alignItems
- center
- justifyContent
- center
- cursor
- pointer
pagination-item-hover
- backgroundColor
- {colors.primary-bg}
- textColor
- {colors.primary}
pagination-item-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
pagination-item-active
- backgroundColor
- {colors.primary}
- textColor
- {colors.primary-text}
- borderColor
- {colors.primary}
pagination-item-disabled
- backgroundColor
- {colors.disabled-bg}
- textColor
- {colors.disabled-text}
- borderColor
- {colors.disabled-border}
- cursor
- not-allowed
pagination-prev-next
- display
- flex
- alignItems
- center
- gap
- {spacing.xs}
- iconSize
- {icons.sizeMap.md}
pagination-ellipsis
- height
- 32px
- minWidth
- 32px
- display
- flex
- alignItems
- center
- justifyContent
- center
- textColor
- {colors.muted}
- fontFamily
- Geist
- fontSize
- 14px
- cursor
- default
- userSelect
- none
pagination-list
- display
- flex
- alignItems
- center
- gap
- {spacing.xs}
pagination-info
- fontFamily
- Geist
- fontSize
- 13px
- fontWeight
- 400
- textColor
- {colors.muted}
pagination-page-size
- display
- flex
- alignItems
- center
- gap
- {spacing.sm}
- fontFamily
- Geist
- fontSize
- 13px
- textColor
- {colors.muted}
Known gaps
- Compact pagination variant (Previous / Next only, no numbered list) — not yet specified.
- Mobile truncation behavior (show only Prev / current / Next on narrow viewports) — not yet specified.