Design system Pickers and overlays

Pagination

Numbered pages in a table or list footer. Previous and Next stay ordinary small ghost buttons; only the numbers wear the square face.

.mk-pagination.mk-page-btn.mk-page-gap

Overview

The page button is one numbered page in a table or list footer. Only the number wears this compact square face — Previous and Next stay ordinary small ghost buttons, because they are actions with words, not positions in a sequence.

Usage

  • Navigate fixed pages of one collectionPlace pagination after a list or table when the complete result set is divided into stable numbered pages.
  • Expose position and relative movementThe current page is read-only; numbered pages move to an absolute position, while Previous and Next move one page.
  • Compress long page setsKeep the first and last page visible and replace omitted runs with a non-interactive ellipsis. Use a stepper or progress pattern for stages in a process.

Anatomy

1 2 3 4
  1. 1Relative moves. Previous and Next are small Quiet buttons with words. They move one slice and never masquerade as numbered square faces.
  2. 2Page face. One 32px square for one absolute position; the surrounding row supplies the 44px target floor. Rest, hover and focus change this same face.
  3. 3Current page. Exactly one face carries aria-current="page", surface fill, camel border and stronger weight. It states position and is not pressable.
  4. 4Ellipsis. A silent, non-interactive gap for omitted pages. First and last positions stay visible around it; the gap is never a jump button.

Variants and states

No sizes, no tiers — pagination is one job at one density.

States

Every state below is the real component wearing that state, not a picture of it.

StateSpecimenWhat changes
Rest A bare number in the muted ink on a transparent square.
Hover A translucent ink wash darkens the square one step on whatever ground the footer sits on — white table foot or grey widget alike.
Keyboard focus The standard 2px camel outline at 2px offset.
Current Surface fill, camel border, weight 600, and the cursor goes default — the current page is a fact, not an action.
At the start The edges never leave: the last page stays one press away. At the edge Previous takes aria-disabled — reachable, refusing, like every blocked action.
Mid-set The current page keeps one neighbour on each side; the ellipsis stands for everything elided. On a narrow screen the same grammar keeps fewer neighbours — the square never shrinks below its 32px face or its 44px target.
At the end The mirror of the start: Next blocks, the first page stays pressable.

Grounds

The white surface is the reference face above. When pagination sits directly on the page ground, its geometry and states do not change; this comparison is kept here as contextual evidence rather than competing with the overview.

On a surface
On the page ground
  • The gap is not a targetThe ellipsis is aria-hidden silence in the quiet ink — never a button. Jumping into the middle is what the footer's number field is for, where a list earns one.

Where the mobile form of a table shows every match, its pagination is deferred with the mobile pass — the component does not improvise a smaller self.

Do and don't

Do

Numbers are squares; the relative moves keep their words and their ghost faces.

Don't

Chevron glyphs dressed as page numbers make the moves and the positions one undifferentiated row of squares.

Copy

  • The number is the labelNothing is added to it — no "page", no punctuation.
  • The moves speakPrevious and Next, full words, sentence case.

Accessibility

  • State, not styleThe current page is aria-current="page"; the visual face follows the attribute.
  • A nav landmarkThe footer's pagination sits in a <nav aria-label="Pages">, so it is one stop, not seven.
  • Targets44px vertically through the expander; the squares sit close but never share a press.

When to reach for something else

  • Moving, not jumpingPrevious and Next are ordinary ghost buttons — they carry words and act relatively.
  • Sections, not pagesSteps of a flow are navigation, not pagination. The number here is a slice of one list, never a stage of a journey.

Markup

<nav aria-label="Pages">
  <button class="mk-btn mk-btn-ghost mk-btn-s mk-btn-inline" type="button">Previous</button>
  <button class="mk-page-btn" type="button">1</button>
  <button class="mk-page-btn" type="button" aria-current="page">2</button>
  <button class="mk-page-btn" type="button">3</button>
  <button class="mk-btn mk-btn-ghost mk-btn-s mk-btn-inline" type="button">Next</button>
</nav>

<!-- a long set: the ellipsis is a silent seat, never a button -->
<nav class="mk-pagination" aria-label="Pages">
  <button class="mk-btn mk-btn-ghost mk-btn-s mk-btn-inline" type="button">Previous</button>
  <button class="mk-page-btn" type="button">1</button>
  <span class="mk-page-gap" aria-hidden="true">…</span>
  <button class="mk-page-btn" type="button">49</button>
  <button class="mk-page-btn" type="button" aria-current="page">50</button>
  <button class="mk-page-btn" type="button">51</button>
  <span class="mk-page-gap" aria-hidden="true">…</span>
  <button class="mk-page-btn" type="button">100</button>
  <button class="mk-btn mk-btn-ghost mk-btn-s mk-btn-inline" type="button">Next</button>
</nav>