Pagination and Load more

Pagination and Load more split long lists, such as a course library, into numbered pages or batches that learners add as they go.

  • Beta
  • Accessibility
  • Figma None (code-only; live library “Load more courses”)
  • packages/ui/src/components/Pagination

Beta until design adds pagination to the Figma design system. Nuton's Figma has none (audit C3), so the whole visual design is code-only, built from Button and segmented-control tokens. The live app uses Load more in the library.

Preview

Change the controls to try every supported combination. Make the window narrow to see the auto variant switch to its compact form.

Links: each page has its own URL (here a #fragment, so the docs page stays put).

tsx
import { Pagination } from '@nuton/ui'

<Pagination
  page={page}
  pageCount={12}
  getHref={(p) => `/library?page=${p}`}
  labels={{ nav: 'Library pages' }}
/>

Installation

Availability

@nuton/ui and @nuton/tokens are private workspace packages and aren't published yet (D4, D12). Inside the Nuton monorepo, add them as workspace dependencies.

bash
pnpm add @nuton/ui @nuton/tokens
tsx
import '@nuton/tokens/tokens.css'
import '@nuton/ui/styles.css'

Usage

tsx
import { LoadMore, Pagination } from '@nuton/ui'

// Each page has its own URL: links
<Pagination page={page} pageCount={12} getHref={(p) => `/library?page=${p}`} labels={{ nav: 'Library pages' }} />

// The list changes in place: buttons
<Pagination page={page} pageCount={12} onPageChange={setPage} />

// Learners add the next batch at the end of the list
<LoadMore shown={courses.length} total={120} loading={isFetching} onLoadMore={fetchNextPage} />
  • Load more suits browsing: a library learners scroll through. It is what the live library uses. Pagination suits finding things again: search results, long tables, anything people share or return to.
  • Prefer links (getHref) when pages have URLs: they work without JavaScript and can open in a new tab. Pass a router's link with linkAs.
  • Name the landmark after what it pages (“Library pages”), especially when a page has more than one navigation.
  • Don't load more automatically on scroll alone. Keyboard and screen-reader users can never reach what comes after the list.
  • Pagination has no hooks, so it renders in server components. LoadMore is a client component.

Variants and sizes

One size. Pagination has three layouts; LoadMore has a count and a button.

Numbered

Previous, page numbers with gaps, Next. The number of slots stays the same as the page changes, so the arrows don't jump.

Compact

“Page 3 of 12” between the arrows, for phones and narrow columns. The default auto variant switches to it below 30rem of its container's width.

First and last

PartMeasurement (code-only)
Page and arrow controls32 × 32 minimum, pill, gap 4; numbers widen for 3 digits
TypeLabel/Small, tabular numbers; arrows are 20px Remix icons
Compact textLabel/Small, Text/Sub-600, 8px each side
LoadMoreCount (Paragraph/Small, Sub-600), 12 gap, Button stroke sm (36 tall)

States and interactions

Current page (code-only)Bg/Strong-950 with white text and aria-current="page".
Unavailable arrow (code-only)Icon/Disabled-300. A link loses its href; a button stays focusable with aria-disabled.
Hover and focus (code-only)Hover: Bg/Weak-50 and Strong-950 text. Focus: the 2px focus-ring tokens.

Showing 24 of 120

Loading (live copy)Spinner and “Loading more…”; presses are ignored and focus stays on the button.

State tokens

Set on .nu-pagination.

TokenValueUsed for
--nu-pagination-size32pxControl size
--nu-pagination-fg · -fg-hover · -bg-hoverText/Sub-600 · Text/Strong-950 · Bg/Weak-50Resting and hover
--nu-pagination-bg-current · -fg-currentAction primary (Bg/Strong-950 · Text/White)Current page
--nu-pagination-fg-disabledIcon/Disabled-300Unavailable arrows

Composition examples

A paged library

Button mode: the list changes in place. Focus stays on the control you used, and the compact text is a polite live region so the new page is announced.

  • Cell structure6 lessons
  • Photosynthesis13 lessons
  • The French Revolution11 lessons
  • Linear equations9 lessons
  • Plate tectonics7 lessons
  • Shakespeare’s sonnets14 lessons
  • Supply and demand12 lessons
  • The water cycle10 lessons
  • Newton’s laws8 lessons
  • Organic chemistry basics6 lessons

Load more courses

The live library pattern. New courses are added after the last one; the count updates and “12 more courses loaded” is announced. When everything has loaded, the button goes and focus moves to the count.

  • Cell structure6 lessons
  • Photosynthesis13 lessons
  • The French Revolution11 lessons
  • Linear equations9 lessons
  • Plate tectonics7 lessons
  • Shakespeare’s sonnets14 lessons
  • Supply and demand12 lessons
  • The water cycle10 lessons
  • Newton’s laws8 lessons
  • Organic chemistry basics6 lessons
  • World War I13 lessons
  • Probability11 lessons

Showing 12 of 48

API reference

Pagination props

Pagination props
PropTypeDefaultDescription
pagenumber—Current page, 1-based. Clamped to 1…pageCount.
pageCountnumber—Total pages. Nothing renders for fewer than 2.
getHref(page) => string—Link mode: every control is a link to this URL.
linkAsElementType—A router's link component for link mode. Receives href, className and ARIA props.
onPageChange(page) => void—Button mode (or an extra click handler in link mode). Not called for the current page or unavailable arrows.
variant'auto' | 'numbers' | 'compact''auto'auto switches from numbers to compact below 30rem of container width.
siblingCountnumber1Pages on each side of the current one.
boundaryCountnumber1Pages always shown at each end.
showFirstLastbooleanfalseAdds First page and Last page arrows.
align'start' | 'center' | 'end''start'Where the controls sit. The nav always fills its container (it is a size container for auto).
labelsPartial<PaginationLabels>—nav ("Pagination"), previous ("Previous page"), next, first, last, page(n) ("Page 3"), pageOf(n, count) ("Page 3 of 12").
…nav propsHTMLAttributes<HTMLElement>—className, id and data attributes go to the nav. The ref points at it.

LoadMore props

LoadMore props
PropTypeDefaultDescription
shownnumber—Items on screen now. An increase is announced.
totalnumber—All items, when known: "Showing 24 of 120".
hasMoreboolean—Whether more can load. Default: shown < total, or true when total is unknown.
loadingbooleanfalseSpinner, "Loading more…" and aria-busy; presses are ignored.
onLoadMore() => void—Load and append the next batch, then update shown.
hideCountbooleanfalseHides the count while more can load. It is still announced.
size · appearanceButtonSize · ButtonAppearance'sm' · 'stroke'Passed to the Button.
fullWidthbooleanfalseStretches the button, for phones.
labelsPartial<LoadMoreLabels>—button ("Load more"), loading ("Loading more…"), count(shown, total), loaded(added, shown, total), done(shown).

Accessibility

Keyboard

KeyBehaviour
TabMoves through the arrows and page numbers, or to the Load more button
EnterFollows a page link or presses a page button
SpacePresses a page button or Load more

Screen readers

A nav landmark named “Pagination” (override it) with a list. Controls are named “Page 3”, “Next page”; the current one has aria-current="page". Gaps are hidden. LoadMore announces “24 more loaded. Showing 48 of 120.” in a polite status region.

Focus

Unavailable buttons stay focusable, so focus isn't lost on the last page. Focus stays on Load more after each batch and moves to the count when the button goes. Ring: the focus-ring tokens.

Contrast and colour

Numbers Sub-600 on white 6.69:1; current page white on Strong-950 17.93:1. The current page differs by fill as well as colour. Forced colours use Highlight for the current page.

Touch and pointer

Every control is at least 32 × 32 (WCAG 2.5.8 asks for 24). The Load more button is 36 tall, or full width with fullWidth.

You must: name the landmark when there is more than one, keep the list's order stable as items are added, and never load more on scroll alone.

Not in Figma

Nuton's Figma file has no pagination or load-more design (audit C3). Everything visual here is code-only and listed in the CSS spec comment for design review.

Guidelines and related components

Use Pagination for

  • Search results and long tables
  • Lists people share or come back to at a given page

Use Load more for

  • Browsing a library or feed
  • Lists where the order matters more than the position

Do and don't

Showing 24 of 120

DoSay how much is left “Showing 24 of 120” tells learners whether to keep going.

Scroll to load more…

Don'tLoad more on scroll alone Endless lists hide the footer and are hard to use with a keyboard or screen reader.
DoName page controls in full “Page 3” and “Next page” make sense out of context, in a links list.

‹ 1 2 3 ›

Don'tUse bare arrows and numbers “3, link” and an unnamed arrow say nothing when read on their own.