The Pagination component enables the user to select a specific page from a range of pages with previous and next controls, collapsing long ranges with ellipses.

Open in new tab
import { useState } from "react";
import { Pagination } from "@kesko/components";

export default () => {
  const [page, setPage] = useState(1);

  return <Pagination count={10} page={page} onChange={setPage} />;
};
import type { CSSProperties } from "react";
import { useState } from "react";
import { Pagination } from "@kesko/components";

const secondary = {
  "--k-pagination-color": "var(--k-color-theme-secondary)",
  "--k-pagination-color-hover": "var(--k-color-highlight-secondary)",
  "--k-pagination-color-text": "var(--k-color-text-on-secondary)",
  "--k-pagination-color-item": "var(--k-color-text-secondary)",
  "--k-pagination-color-tint": "var(--k-color-bg-secondary)",
  "--k-button-radius": "var(--k-radius-sm)",
} as CSSProperties;

export default () => {
  const [page, setPage] = useState(5);

  return <Pagination count={20} page={page} onChange={setPage} style={secondary} />;
};
import { useState } from "react";
import { Pagination } from "@kesko/components";

export default () => {
  const [page, setPage] = useState(1);

  return <Pagination count={5} page={page} onChange={setPage} hidePrevious hideNext />;
};
import { useState } from "react";
import { Pagination } from "@kesko/components";

export default () => {
  const [page, setPage] = useState(1);

  return <Pagination count={5} page={page} onChange={setPage} hideLabels />;
};
import { useState } from "react";
import { Pagination } from "@kesko/components";

export default () => {
  const [page, setPage] = useState(3);

  return (
    <Pagination
      count={10}
      page={page}
      onChange={setPage}
      previousLabel="Newer posts"
      nextLabel="Older posts"
      pageLabel={page => `Page ${page}`}
    />
  );
};
import { useState } from "react";
import { Pagination, Stack } from "@kesko/components";

const PER_PAGE = 3;
const PRODUCTS = Array.from({ length: 12 }, (_, i) => `Product ${i + 1}`);

export default () => {
  const [page, setPage] = useState(1);
  const count = Math.ceil(PRODUCTS.length / PER_PAGE);
  const visible = PRODUCTS.slice((page - 1) * PER_PAGE, page * PER_PAGE);

  return (
    <Stack gap="md" align="stretch">
      <ul>
        {visible.map(name => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <Pagination count={count} page={page} onChange={setPage} aria-label="Search results pages" />
    </Stack>
  );
};
import { useState } from "react";
import { Pagination, Stack } from "@kesko/components";

export default () => {
  const [page, setPage] = useState(3);

  return (
    <Stack gap="lg" align="stretch">
      <Pagination count={10} page={page} onChange={setPage} size="sm" aria-label="Small" />
      <Pagination count={10} page={page} onChange={setPage} aria-label="Medium" />
      <Pagination count={10} page={page} onChange={setPage} size="lg" aria-label="Large" />
    </Stack>
  );
};
import { useState } from "react";
import { Pagination } from "@kesko/components";

export default () => {
  const [page, setPage] = useState(10);

  return <Pagination count={20} page={page} siblingCount={2} boundaryCount={2} onChange={setPage} />;
};

Properties

NameDescriptionTypeDefault
count

Total number of pages.

number—
size

Size of the page and control buttons.

"sm" | "md" | "lg""md"
siblingCount

Number of always-visible page buttons on each side of the active page.

number1
boundaryCount

Number of always-visible page buttons at the start and end of the range.

number1
pageLabel

Overrides the translated accessible name of a numbered page button, “Sivu 3” by default. Previous and next are named by their visible labels.

(page: number) => string—
previousLabel

Label for the previous-page button: visible text, or the accessible name when hideLabels is set.

string—
nextLabel

Label for the next-page button: visible text, or the accessible name when hideLabels is set.

string—
hideLabels

Shows the previous and next controls as icon-only square buttons, named by previousLabel and nextLabel.

booleanfalse
hidePrevious

Hides the previous-page button.

booleanfalse
hideNext

Hides the next-page button.

booleanfalse
page

The currently active page (1-based).

number—
aria-label

Accessible label for the navigation landmark.

string—
href

Returns the href for a page item as well as next and prev buttons, rendering the items as links.

(paginationItem: PaginationItem) => string—

Events

NameDescriptionType
onChange

Called with the newly selected page and the originating click event.

(page: number, event: MouseEvent<ButtonTarget>) => void

Styles

CSS custom properties provide finer-grained control than props. Prefer the props API when it suits your need.

NameDescriptionDefault
--k-pagination-column-gap

Overrides the horizontal gap between items.

var(--k-space-3xs)
--k-pagination-row-gap

Overrides the vertical gap between the page list and the controls.

var(--k-space-2xs)
--k-pagination-color

Overrides the current page background. Also colors the text of the other items unless --k-pagination-color-item is set.

var(--k-color-theme-primary)
--k-pagination-color-hover

Overrides the hover and pressed background of the current page. Also colors the hover text of the other items unless --k-pagination-color-item or --k-pagination-color-item-hover is set.

var(--k-color-highlight-primary)
--k-pagination-color-text

Overrides the text color on the current page.

var(--k-color-text-on-primary)
--k-pagination-color-item

Overrides the text of the other pages, the controls and the ellipsis. Set it when the accent is a fill that is not readable as text, as the secondary color is in dark mode.

var(--k-color-text-primary)
--k-pagination-color-item-hover

Overrides the hover and pressed text of the other pages and controls.

var(--k-pagination-color-item)
--k-pagination-color-tint

Overrides the hover and pressed background of the other pages and controls.

var(--k-color-bg-primary)

Accessibility #

Under the hood this is a <nav> landmark holding native buttons, or links when you pass href, so people can jump to it, step through it and hear which page is current.

  • Name it when there are several. Every pagination is a navigation landmark called “Sivutus” by default. When a page has more than one, for example search results and comments, pass a distinct aria-label to each, such as “Hakutulosten sivutus”, so a landmark list tells them apart.
  • Prefer href when pages are URLs. Links give people a real address: the back button works, the page can be opened in a new tab and screen readers announce a link to a page rather than a button that does something.
  • Decide what the page buttons say. A page button is named “Page {page}” by default and follows the language like the other built-in text. The name keeps the visible number, so saying “click 3” still works with voice control. Pass pageLabel to override it per instance.
  • Previous and next are named by their visible text. Screen readers read the “Edellinen” and “Seuraava” text as it is shown, and the component has no prop for a different hidden name. This is on purpose: someone using voice control activates a button by saying its visible text, for example “click Edellinen”, and that only works when the accessible name contains the visible text.
  • Keep the controls where they are. Previous and next stay focusable when there is nowhere to go; they are marked aria-disabled instead of removed or disabled, so focus is never lost when someone reaches the first or last page.
  • Keep it on one row on small screens. The page list never wraps, so a wide range overflows a narrow viewport. Lower siblingCount or boundaryCount, or pass size="sm", where the component has less than about 360px to work with.
  • Check contrast when you override the item color. --k-pagination-color-item paints the other pages, the controls and the ellipsis, and needs 4.5:1 against the page background and against --k-pagination-color-tint.

See the accessibility guidelines and the A11Y checklist for more details.


Usage #

There are two necessary parts to using the Pagination component. The @kesko/components package and the CSS Framework are required. Everything else is optional:

npm install @kesko/components @kesko/css

Once installed, load the CSS Framework in your app’s entry point or root layout:

import "@kesko/css";

Then, you can import the Pagination:

import { Pagination } from "@kesko/components";

Projects without React can get the same styles from @kesko/css by importing the components stylesheet as well:

@import "@kesko/css";
@import "@kesko/css/components";

Resources #