Pagination

Pagination allows users to navigate through large amounts of content or data divided across multiple pages while making it clear that more pages exist.

GitHubStorybook
yarn add @workday/canvas-kit-react
Sources
GitHubStorybook
Install
yarn add @workday/canvas-kit-react

Anatomy

Image of a Pagination component with annotation markers.

  1. Page Numbers: Each page has its own page number. All page numbers inherit styling and interactions from our Icon Only Primary Button Variant.
  2. Navigational Controls: Allow users to navigate through available pages. These controls inherit styling and interactions from our Icon Only Tertiary Button. Previous/Next uses Small Chevron System Icon and First/Last uses Chevron x2 Small System Icon.
  3. Input w/ Label: Shows a box adjacent to the pagination bar where a page can be entered and is submitted when 'Enter' key is pressed. Enables users to jump to specific page. Input uses the Canvas Text Input with browser-specific numeric input override.
  4. Additional Details: Communicates the number of pages and total number of items. Page numbers are separated by an en dash. The word “pages” is customizable.

Pagination is a highly configurable component so we’ve intentionally built each piece of this component separately so you may compose it in a variety of ways.

Usage Guidance

  • Pagination is a highly configurable navigation component composed of the following elements:
    • Page Numbers: Provide context about what page the user is on in relation to other pages.
    • Page Navigation: Navigational controls allow users to navigate back or forward through pages. If applicable, they are also able to jump to the first or last page.
    • Numeric Input: Enables users to jump to a specific page.
    • Label: Communicates the number of pages and total number of items. The word “items” is customizable.
  • Although the component is typically placed below the corresponding content or data, there is flexibility on placement depending on your use case.
  • Pagination is typically used with Tables.
  • In responsive experiences, Pagination condenses to its most basic elements (Left/Right Controls, Current Page Number).

When to Use

  • To divide large quantities of data or content into chunks.
  • To improve the loading performance of a system.
  • To make user comprehension of data/content less overwhelming.
  • To enable all users to navigate to through pages or locate a specific page number.
  • To show how many pages of content there are and how many results have been returned.

Best Practices

  • Display at least one way for users to navigate through pages. If possible, provide more than one option (eg. Previous/Next controls and a Numeric Input).
  • Display the Current Page to provide awareness of location in relation to the other pages.
  • If feasible, display additional details (number of pages and total number of items) to communicate even more context about the content or data the user is paging through.

Each element of Pagination has been built separately so you may compose it in a variety of ways. How you decide to put this component together will likely depend on the reality of the technical constraints that exist and your user needs.

Below are some examples of how you can configure Pagination based on screen size or technical considerations. These recommendations are not exhaustive.

Based on Screen Size:

Screen Size diagrams for Pagination

  • For smaller screens, it’s recommended to show page navigation controls with no more than 3 pages.
  • For larger screens, you can show up to 5 pages, both types of page navigation controls, and a numeric input with additional details.

Based on Technical Considerations:

Technical Considerations for pagination

  • If your API is unable to return a total page number, we recommend showing the Previous/Next page navigation controls, the Current Page Number, and up to 4 other Page Numbers (if possible).
  • If your API is able to return a known number of pages, composability options are endless. You can essentially pair any combination of elements together to create a solution that is best for your use case.

Examples

Basic Example

Pagination includes a container Pagination component and a number of subcomponents which can be composed in a variety of ways.

In this example, we set up a basic Pagination component with the default range of five pages, as well as step controls (Pagination.StepToPreviousButton and Pagination.StepToNextButton) that allow you to move to the previous page or the next page.

Note that you must include Pagination.AdditionalDetails to meet accessibility standards (with one exception, see Pagination.AdditionalDetails for more information). It is an aria-live region that announces the current page update to screen readers. If you wish to prevent it from displaying (as we've done in the remaining examples), you may set its shouldHideDetails prop to true. The visually hidden region will still be accessible to screen readers.

Hoisted Model

By default, Pagination will create and use its own model internally. Alternatively, you may configure your own model with usePaginationModel and pass it to Pagination via the model prop. This pattern is referred to as hoisting the model and provides direct access to its state and events outside of the Pagination component.

In this example, we set up external observation of the model state and create an external button to trigger an event to change the current page.

Jump Controls

This example adds jump controls (Pagination.JumpToFirstButton and Pagination.JumpToLastButton) that allow you to skip to the first and last pages in the range.

GoTo Form

This example adds a form (Pagination.GoToForm) that allows you to skip to a specific page within the range.

Right-to-Left (RTL)

Pagination supports right-to-left languages when specified in the CanvasProvider theme.

Custom Range

This example uses a custom range that allows you to control the width of the component.

Component API

Pagination

Usage

Pagination is a container component rendered as a <nav> element that is responsible for creating a PaginationModel and sharing it with its subcomponents using React context.

<Pagination
aria-label="Pagination"
lastPage={100}
initialCurrentPage={6}
rangeSize={3}
onPageChange={pageNumber => console.log(pageNumber)}
>
{/* Child components */}
</Pagination>

Alternatively, you may pass in a model using the hoisted model pattern.

const model = usePaginationModel({
lastPage: 100,
initialCurrentPage: 6,
rangeSize: 3,
onPageChange: pageNumber => console.log(pageNumber),
});
return (
<Pagination aria-label="Pagination" model={model}>
{/* Child components */}
</Pagination>
);

Props

Undocumented props are spread to the underlying <nav> element.

NameTypeDefaultDescription
lastPage*numberThe page number for the last page (it can also be used as a total page count)
firstPagenumber | undefined1The page number for the first page
initialCurrentPagenumber | undefined1The initial current page
onPageChange((pageNumber: number) => void) | undefinedThe function called when the page changes
rangeSizenumber | undefined5The size of the pagination range

If you're using the hoisted model pattern, follow this table instead (refer to the Model section for more information about PaginationModel):

NameTypeDefaultDescription
model*PaginationModel

Note that you must set aria-label in either case to meet accessibility standards. We recommend setting it to Pagination or the translated equivalent.

Pagination.Controls

Usage

Pagination.Controls is a styled <div> element that provides proper alignment and spacing between elements inside Pagination. It does not handle any internal business logic or state.

<Pagination.Controls>{/* Child components */}</Pagination.Controls>

Props

Pagination.Controls does not have any documented props.

Undocumented props are spread to the underlying <div> element.

Pagination.JumpToFirstButton

Usage

Pagination.JumpToFirstButton is an Button that subscribes to the Pagination context. This allows it to know when to disable and set currentPage to the first page.

<Pagination.JumpToFirstButton aria-label="First" />

Props

Undocumented props are spread to the underlying <button> element. Note that you must set aria-label to meet accessibility standards. We recommend setting it to First or the translated equivalent.

Pagination.JumpToFirstButton supports all TertiaryButton props.

Pagination.StepToPreviousButton

Usage

Pagination.StepToPreviousButton is an TertiaryButton that renders an icon that subscribes to the Pagination context. This allows it to know when to disable and decrement the currentPage.

<Pagination.StepToPreviousButton aria-label="Previous" />

Props

Undocumented props are spread to the underlying <button> element. Note that you must set aria-label to meet accessibility standards. We recommend setting it to Previous or the translated equivalent.

Pagination.StepToPreviousButton supports all TertiaryButton props.

Pagination.StepToNextButton

Usage

Pagination.StepToNextButton is an TertiaryButton that subscribes to the Pagination context. This allows it to know when to disable and increment the currentPage.

<Pagination.StepToNextButton aria-label="Next" />

Props

Undocumented props are spread to the underlying <button> element. Note that you must set aria-label to meet accessibility standards. We recommend setting it to Next or the translated equivalent.

Pagination.StepToNextButton supports all TertiaryButton props.

Pagination.JumpToLastButton

Usage

Pagination.JumpToLastButton is an TertiaryButton that renders an icon that subscribes to the Pagination context. This allows it to know when to disable and set currentPage to the last page.

<Pagination.JumpToLastButton aria-label="Last" />

Props

Undocumented props are spread to the underlying <button> element. Note that you must set aria-label to meet accessibility standards. We recommend setting it to Last or the translated equivalent.

Pagination.JumpToLastButton supports all TertiaryButton props.

Pagination.PageList

Usage

Pagination.PageList is an <ol> element that subscribes to the Pagination context. This allows it generate the range of page numbers. It also handles spacing between the child elements.

This component will accept either child elements or a render prop. In most cases, you'll want to use the render prop so you can access the Pagination model in order to generate the proper list items.

<Pagination.PageList>
{({state}) =>
state.range.map(pageNumber => (
<Pagination.PageListItem key={pageNumber}>
<Pagination.PageButton aria-label={`Page ${pageNumber}`} pageNumber={pageNumber} />
</Pagination.PageListItem>
))
}
</Pagination.PageList>

Props

Undocumented props are spread to the underlying <ol> element.

NameTypeDefaultDescription
children*(model: PaginationModel) => ReactNodeAccepts child elements or a render prop.

Pagination.PageListItem

Usage

Pagination.PageListItem is a styled <li> element. It provides a semantic child element for the PageList component and is important for accessibility. It does not handle any internal business logic or state.

<Pagination.PageListItem>{/* Child element */}</Pagination.PageListItem>

Props

Pagination.PageListItem does not have any documented props.

Undocumented props are spread to the underlying <li> element.

Pagination.PageButton

Usage

Pagination.PageButton is a Button that subscribes to the Pagination context. This allows it to update the currentPage and set the toggled styling when it is the current item.

Pagination.PageButton will render pageNumber as its children.

<Pagination.PageButton aria-label="Page 2" pageNumber={2} />

Props

Undocumented props are spread to the underlying <button> element. Note that you must set aria-label to meet accessibility standards. We recommend setting it to Page ${pageNumber} or the translated equivalent.

NameTypeDefaultDescription
pageNumber*number

Pagination.PageButton also supports all Button props.

Pagination.GoToForm

Usage

Pagination.GoToForm is a wrapper for a React context provider rendered as a <form> element. Child components such as Pagination.GoToTextInput and Pagination.GoToLabel subscribe to that context to manage the form state and behavior as well as update the currentPage in the Pagination component.

<Pagination.GoToForm>{/* Child elements */}</Pagination.GoToForm>

Props

Pagination.GoToForm does not have any documented props.

Undocumented props are spread to the underlying <form> element.

Pagination.GoToTextInput

Usage

Pagination.GoToTextInput is a TextInput.

<Pagination.GoToTextInput aria-label="Go to page number" />

Props

Undocumented props are spread to the underlying <input type="text"> element. Note that you must set aria-label to meet accessibility standards. We recommend setting it to Go to page number or the translated equivalent.

Pagination.GoToTextInput supports all TextInput props.

Pagination.GoToLabel

Usage

Pagination.GoToLabel is a styled <label> element that subscribes to the Pagination context. This allows it to pass the Pagination context to child elements.

This component will accept either child elements or a render prop. In most cases, you'll want to use the render prop so you can access the Pagination model when generating the label text.

<Pagination.GoToLabel>{({state}) => `of ${state.lastPage} pages`}</Pagination.GoToLabel>

Props

Undocumented props are spread to the underlying <label> element.

NameTypeDefaultDescription

Pagination.AdditionalDetails

Usage

Pagination.AdditionalDetails is a styled <div> element that subscribes to the Pagination context. This allows it to pass the Pagination context to child elements. It is also an aria-live region that announces the current page update to screen readers.

Pagination.AdditionalDetails must be included in your Pagination component to meet accessibility standards (with one exception, see below). If you wish to prevent it from displaying, you may set its shouldHideDetails prop to true. The visually hidden region will still be accessible to screen readers.

If you have multiple Pagination components sharing the same state and rendered on the same page, you may do either of the following to prevent screen readers from announcing the same update multiple times:

  • Exclude Pagination.AdditionalDetails from all but one of the Pagination components. This is the one case where you may exclude Pagination.AdditionalDetails from a Pagination component.
  • Include Pagination.AdditionalDetails in every Pagination component (i.e., you want it to be visible for every component), but set the shouldAnnounceToScreenReader prop to false on all but one of them.

This component will accept either child elements or a render prop. In most cases, you'll want to use the render prop so you can access the Pagination model in order to generate the appropriate text.

<Pagination.AdditionalDetails>
{({state}) =>
`${getVisibleResultsMin(state.currentPage, resultCount)}-${getVisibleResultsMax(
state.currentPage,
resultCount,
totalCount
)} of ${totalCount} results`
}
</Pagination.AdditionalDetails>

Props

Undocumented props are spread to the underlying <div> element.

NameTypeDefaultDescription
children*(model: PaginationModel) => ReactNodeAccepts child elements or a render prop.
shouldAnnounceToScreenReaderboolean | undefinedtrue
shouldHideDetailsboolean | undefinedfalse

Model

If Pagination was stripped of all its markup, attributes, and styling, what would remain is the model. The model is an object composed of two parts: state which describes the current snapshot in time of the component and events which describes events that can be sent to the model.

By default, Pagination will create a model and share it internally with its subcomponents using React context. You may subscribe to PaginationContext if you wish to create a custom subcomponent for your implementation. Here's a simple example.

import * as React from 'react';
import {Pagination, PaginationContext} from '@workday/canvas-kit-react/pagination';
const CustomButton = (props: React.HTMLAttributes<HTMLButtonElement>) => {
const model = React.useContext(PaginationContext);
const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
// If onClick is provided, pass the event along
props.onClick?.(e);
model.events.goTo(10);
};
return (
<button onClick={handleClick} {...props}>
Go To Page 10
</button>
);
};
export const CustomPagination = () => {
return (
<Pagination aria-label="Pagination" lastPage={20}>
<CustomButton aria-label="Page 10" />
{/* Other subcomponents */}
</Pagination>
);
};

Alternatively, if you need direct access to the model's state and events outside of the Pagination component, you may configure your own model with usePaginationModel and pass it to Pagination via a pattern called hoisting the model.

const model = usePaginationModel({
lastPage,
onPageChange: number => console.log(number),
});
<Pagination aria-label="Pagination" model={model}>
{/* Child components */}
</Pagination>;

Config

usePaginationModel accepts a configuration object with the following properties and returns a PaginationModel with state and events properties.

NameTypeDefaultDescription
lastPage*numberThe page number for the last page (it can also be used as a total page count)
firstPagenumber | undefined1The page number for the first page
initialCurrentPagenumber | undefined1The initial current page
onPageChange((pageNumber: number) => void) | undefinedThe function called when the page changes
rangeSizenumber | undefined5The size of the pagination range

State

The PaginationModel state is an object with the following properties.

NameTypeDefaultDescription
currentPage*numberThe page number for the current page
firstPage*numberThe page number for the first page
lastPage*numberThe page number for the last page (it can also be used as a total page count)
range*number[]An array of page numbers included in the pagination range
rangeSize*numberThe size of the pagination range

Events

The PaginationModel events is an object with the following properties.

NameTypeDefaultDescription
setCurrentPage*(page: number) => voidSets the current page to a given page number (no safeguards)
first*() => voidSets the current page to the first page
last*() => voidSets the current page to the last page
next*() => voidIncrements the current page by 1
previous*() => voidDecrements the current page by 1
goTo*(page: number) => voidSets the current page to a given page number (with safeguards). `goTo` is very similar to `setCurrentPage`, but it has some built-in safeguards. If the page number provided is below the first page (e.g: `0`), `currentPage` will be set to the `firstPage`. Similarly, if the number provided is larger than the `lastPage`, it will set `currentPage` to the `lastPage`.

Utilities

getLastPage

This function takes the number of results per page and the total count of results and returns the last page number. Here's an example:

Given there are 10 results per page, and there are 128 total results, the function will return 13.

const resultCount = 10;
const totalCount = 128;
const lastPage = getLastPage(resultCount, totalCount); //=> 13

getRangeMin

This function takes the pagination range and returns its minimum value. Here's an example:

Given the pagination range is 1-5, the function will return 1.

const range = [1, 2, 3, 4, 5];
const rangeMin = getRangeMin(range); //=> 1

getRangeMax

This function takes the pagination range and returns its maximum value. Here's an example:

Given the pagination range is 1-5, the function will return 5.

const range = [1, 2, 3, 4, 5];
const rangeMin = getRangeMax(range); //=> 5

getVisibleResultsMin

This function takes the current page, and number of results per page, and returns the minimum value for that page. Here's an example:

Given there are 10 results per page, and the current page is 5, the function will return 41.

const currentPage = 5;
const resultCount = 10;
const pageMin = getVisibleResultsMin(currentPage, resultCount); //=> 41

getVisibleResultsMax

This function takes the current page, number of results per page, and the total number of results, and returns the maximum value for that page. Here's an example:

Given there are 10 results per page, the current page is 5, and there are 42 results total, the function will return 42.

const currentPage = 5;
const resultCount = 10;
const totalCount = 42;
const pageMax = getVisibleResultsMax(currentPage, resultCount, totalCount); //=> 42

Accessibility Guidelines

  • Pagination buttons that do not have visual text labels must have offscreen labels to communicate to a screen reader user what action they perform. For example: the “Next” button could have an aria-label that says “Next page of results.”
  • When writing screen reader labels for pagination controls, do not include the word ‘button’ in the label.
  • Ensure each pagination button has a unique screen reader label for the context that it will be added to. Multiple buttons with different behavior on the same page must have different screen reader labels, even if visually they look the same.
  • All non-disabled pagination buttons must be able to receive keyboard focus and be actionable with space and enter keys.
  • When a user navigates to a page using the form input, focus should be placed on the newly loaded page.
  • Paginating between results is navigational, a pagination component should be encapsulated within a <nav> region.
  • All foreground text must meet a contrast ratio of 4.5:1 in relation to its background color.
  • All icons and states must meet a contrast ratio of 3:1 in relation to their adjacent colors.

Content Guidelines

Content

  • If customizing the label of the total number of items, use a word that accurately describes the items that are being shown.
  • Placement of the label is flexible.

Can't Find What You Need?

Check out our FAQ section which may help you find the information you're looking for.

FAQ Section