Use Pagination to let users navigate through content broken down into pages. Usually paired with tables, but works with other types of page content.
Usage
When to use
- To break down large content into pages. Usually paired with tables, but works with other types of page content.
When not to use
- As a navigation control for a flow or to pair with a stepper, i.e. for a guide, tutorial, or setup flow.
- As a controller to switch between multiple views. Use Tabs instead.
Numbered vs Compact
Cursor and offset are the most common types of pagination. Currently, most i80 products use cursor-based pagination.
Cursor-based pagination allows users to navigate to the next or previous set of records no matter where the user is located within the dataset (record 1 or 300). This type of pagination uses the latest record that has been delivered to the client from a database to determine the relative location within the data set, rather than the exact page number.
Offset or page-based pagination divides a dataset into pages containing a default or user-determined number of records, and allows users navigate to any particular page. In most cases, the numbered pagination provides a better user experience. It allows users to jump between pages and always return to the first page or go to the last page without navigating through the pages manually.


Truncation
By default, in Numbered Pagination, the number of visible pages will be truncated when the total number of pages exceeds seven. What pages are truncated depends on the current page the user is on, with a few notable constants:
- The first and last page will always be displayed (never be truncated).
- The previous page and next page compared to the current page will always be displayed (unless the current page is the first or last page).
- A maximum of seven pages or truncated pages will always be displayed.
The number of pages equals total items / items per page, e.g., if the total number of items is 120 and there are 10 items per page, the number of pages is 12. However, this varies with the chosen page size, which can decide whether the number of pages extends beyond the threshold of truncation.
Current page examples
These examples showcase where truncation will occur depending on what page the user is on; at the start, middle, or end of Pagination.



When to use truncation
Truncation can help to reduce the cognitive load on the user by only displaying immediately relevant pages to navigate between; those directly surrounding the current page, and the first/last page.
While not intended to be used as a solution for a responsive layout, truncation can help to save space if there are many pages.
When not to use truncation
Truncation can have a negative impact on the user experience if navigating to a specific page is required, or if seeing all of the pages at once benefits the user.
Spacing
- When using the pagination bar, the container should be flush on the left & right with the content.
- When using the pagination, the component should be center aligned with the content it relates to.
- Make sure there’s enough distance and breathing room between the pagination and unrelated content (e.g. another section below it), so it’s clear what content the pagination is paired with.
When pairing the pagination or pagination bar with your content, we recommend leaving 16px of margin between the pagination and the content it relates to.
If your product uses a significantly higher or lower spacing scale, increase or decrease the spacing accordingly.


Pagination and filtering
While pagination can be beneficial for dividing up and displaying a large dataset into more manageable chunks, relying solely on pagination and sorting to find a specific record or set of records results in a poor user experience. This is especially true in cursor-based pagination, where it may not be clear to the user where their relative position is within the dataset.
Reflect filtered numbers
When filtering, the data set will limit the number of returned results and should be reflected in the pagination's total number count.
Compact vs Numbered Pagination
There are two different variants of the Pagination component, built to cover different use cases, contexts, and designs you may need them for. They are the type attribute on <i80-pagination>: numbered (the default) or compact.
This differentiation is necessary to cover both use cases of pagination for a list with a known number of elements (i.e., "numbered") and one in which this information is not available or is cursor-based (i.e., "compact").
In the first one, the user is presented with a list of navigation controls ("prev/next" and "page numbers" to go directly to a specific page) and other optional UI elements; in the second, much simpler one, the user is presented with only the "prev/next" controls (by default).
When pagination is used this way, it will automatically:
- provide the correct responsive layout for the entire Pagination and its sub-parts.
- manage the "current page" status across the different sub-parts it’s made of, based on the attributes provided to it.
- when one of the "navigation controls" is clicked, fire an
i80-pageevent, or follow the link whenhref-templateis set. - when the "page size" is changed via the provided selector, in the "numbered" variant it will automatically recalculate the total number of pages to display to the user.
Events handling and routing
As described above, <i80-pagination> fires an i80-page event whenever a page change occurs, with {page, perPage} in its detail. All the "navigation controls" in this case are <button> elements.
This means that if you need to update the URL when the user changes the "page" in the Pagination (eg. to add/remove/update some query parameters), you have to do it when you handle that event.
If instead you need to update the URL directly when the user clicks on one of the "navigation control" elements, set href-template to a URL with {page} and {perPage} in it, and the controls are rendered as links.
How to use Numbered Pagination
Numbered pagination requires the number of items: the total attribute, plus the event or link handling, see below:
<i80-pagination total="40" per-page="10" show-info show-size-selector></i80-pagination>
Add show-info and show-size-selector to display the info text and the page-size selector. The component takes care of updating the values and the states of the different elements, according to the user interactions with the component.
Extra options
It’s possible to customize the info, controls, and size selector with attributes on the element: page, per-page, page-sizes and size-label. For more details about these parameters, refer to the "Component API" section.
Below is an example of some of these extras:
<i80-pagination total="40" per-page="20"></i80-pagination>
Example of custom label text for the size selector: the size-label attribute
<i80-pagination total="40" per-page="10" show-info show-size-selector size-label="Per page"></i80-pagination>
Truncation
When there is a large number of items and consequently the number of pages is also large, by default the component automatically "truncates" the number of visible pages (using "ellipses"):
<i80-pagination total="120" per-page="10" show-info show-size-selector></i80-pagination>
Routing/URL updates
If you want the Pagination to change the URL of the page directly (eg. updating the query parameters) you need to set href-template to a URL with {page} and {perPage} in it:
<i80-pagination
total="40"
page="1"
per-page="5"
page-sizes="5,10,30"
show-info
show-size-selector
href-template="?page={page}&per={perPage}"
></i80-pagination>
When href-template is set, the "navigation controls" are rendered as links and if the user clicks on one of them the page URL is automatically updated. This means that the component’s state is persisted outside of the component and so its whole state must be "controlled" by the consumer’s code (otherwise there would be conflicting states).
In this case, the component doesn’t update its internal "state" but the value of the state variables (eg. the current page and page size) is always determined by the page that renders the element (usually, they are directly connected to the query parameters in the URL).
The {page} and {perPage} placeholders in href-template exist because the URL is dynamic (it depends on the current page) and lets you specify your own query parameters (which will likely differ case by case).
Even when the Pagination is based on links, the size selector still fires i80-page-size, which can be used to respond to the users’ actions (eg. for logging, tracking, etc.).
Below you can find an example of an integration between the sortable Table component and numbered pagination that uses query parameters in the URL to preserve the UI state:
| ID | Name | Role | |
|---|---|---|---|
| 1 | Burnaby Kuscha | 1_bkuscha0@tiny.cc | Owner |
| 2 | Barton Penley | 2_bpenley1@miibeian.gov.cn | Admin |
| 3 | Norina Emanulsson | 3_nemanulsson2@walmart.com | Contributor |
| 4 | Orbadiah Smales | 4_osmales3@amazon.co.jp | Contributor |
| 5 | Dido Titchener | 5_dtitchener4@blogs.com | Contributor |
<div style="display: grid; gap: 16px">
<i80-table>
<table>
<thead>
<tr>
<th sort>ID</th>
<th sort>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Burnaby Kuscha</td>
<td>1_bkuscha0@tiny.cc</td>
<td>Owner</td>
</tr>
<tr>
<td>2</td>
<td>Barton Penley</td>
<td>2_bpenley1@miibeian.gov.cn</td>
<td>Admin</td>
</tr>
<tr>
<td>3</td>
<td>Norina Emanulsson</td>
<td>3_nemanulsson2@walmart.com</td>
<td>Contributor</td>
</tr>
<tr>
<td>4</td>
<td>Orbadiah Smales</td>
<td>4_osmales3@amazon.co.jp</td>
<td>Contributor</td>
</tr>
<tr>
<td>5</td>
<td>Dido Titchener</td>
<td>5_dtitchener4@blogs.com</td>
<td>Contributor</td>
</tr>
</tbody>
</table>
</i80-table>
<i80-pagination
total="40"
page="1"
per-page="5"
page-sizes="5,10,30"
show-info
show-size-selector
href-template="?page={page}&per={perPage}"
></i80-pagination>
</div>
How to use Compact Pagination
Compact pagination — type="compact" — doesn’t require any other attributes (apart from the event or link handling, see below). Use has-previous and has-next to say which arrows are active, which is what cursor-based paging needs:
<i80-pagination type="compact" has-previous has-next></i80-pagination>
In this variant, only the "prev" and "next" navigation controls are displayed.
Extra options
It is also possible to show the size selector (hidden by default) with show-size-selector:
<i80-pagination type="compact" has-previous has-next show-size-selector></i80-pagination>
Routing/URL updates
If you want the Pagination to change the URL of the page directly (eg. updating the query parameters) you need to set href-template to a URL with {page} and {perPage} in it.
When href-template is set, the "prev/next" controls are rendered as links and the page URL is automatically updated when the user clicks them. This means that the component’s state is persisted outside of the component and so its whole state must be "controlled" by the consumer’s code (otherwise there would be conflicting states).
In this case, the component doesn’t update its internal "state" but the value of the state variables is always determined by the page that renders the element (usually, they are directly connected to the query parameters in the URL).
The {page} and {perPage} placeholders in href-template exist because the URL is dynamic (it depends on the current page or cursor) and lets you specify your own query parameters (which will likely differ case by case).
Even when the Pagination is based on links, the size selector still fires i80-page-size, which can be used to respond to the users' actions (eg. for logging, tracking, etc.).
Below you can find an example of an integration between the Table component and compact pagination that uses query parameters in the URL to preserve the UI state:
| ID | Name | Role | |
|---|---|---|---|
| 1 | Burnaby Kuscha | 1_bkuscha0@tiny.cc | Owner |
| 2 | Barton Penley | 2_bpenley1@miibeian.gov.cn | Admin |
| 3 | Norina Emanulsson | 3_nemanulsson2@walmart.com | Contributor |
| 4 | Orbadiah Smales | 4_osmales3@amazon.co.jp | Contributor |
| 5 | Dido Titchener | 5_dtitchener4@blogs.com | Contributor |
<div style="display: grid; gap: 16px">
<i80-table>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Burnaby Kuscha</td>
<td>1_bkuscha0@tiny.cc</td>
<td>Owner</td>
</tr>
<tr>
<td>2</td>
<td>Barton Penley</td>
<td>2_bpenley1@miibeian.gov.cn</td>
<td>Admin</td>
</tr>
<tr>
<td>3</td>
<td>Norina Emanulsson</td>
<td>3_nemanulsson2@walmart.com</td>
<td>Contributor</td>
</tr>
<tr>
<td>4</td>
<td>Orbadiah Smales</td>
<td>4_osmales3@amazon.co.jp</td>
<td>Contributor</td>
</tr>
<tr>
<td>5</td>
<td>Dido Titchener</td>
<td>5_dtitchener4@blogs.com</td>
<td>Contributor</td>
</tr>
</tbody>
</table>
</i80-table>
<i80-pagination type="compact" has-next></i80-pagination>
</div>
Attributes
<i80-pagination> renders this component. Everything it understands:
| Attribute | Values | Default | Notes |
|---|---|---|---|
total |
number | Number of items. Leave it out when you don't know it (use type="compact"). | |
page / per-page |
number | 1 / 25 |
Event: i80-page with {page, perPage}. |
type |
numbered · compact | numbered |
Compact has arrows only. |
has-next / has-previous |
boolean | Override which arrows are active, e.g. for cursor paging. | |
href-template |
URL with {page} and {perPage} | Renders links instead of buttons. | |
show-info |
boolean | Shows "51–75 of 248". | |
show-size-selector, page-sizes, size-label |
boolean, list, text | 10,25,50,100 |
Items-per-page select. Event: i80-page-size. |
aria-label |
text | Pagination |
The name of the navigation landmark. |
The tag keeps any native element you write inside it, so a server-rendered form, link or button works as it is. See Web Components for loading i80.js and the reference for these examples running.
Anatomy
Pagination

| Element | Usage |
|---|---|
| Info | Optional |
| Nav | Required |
| Size selector | Optional |
Pagination Nav

| Element | Usage |
|---|---|
| Prev page | Required |
| Next page | Required |
| Page number | Required for Compact, otherwise optional |
| Current page | Required |
Pagination Info

| Element | Usage |
|---|---|
| Page items | Required |
| Total items | Optional |
Size Selector

| Element | Usage |
|---|---|
| Label | Required |
| Select | Required |
States
Default

Selected

Conformance rating
When used as recommended, there should not be any WCAG conformance issues with this component.
Best practices
Keyboard navigation
In most cases, the numbered pagination provides a greater user experience. It allows users to jump between pages and always return to the first page or go to the last page without navigating through the pages manually.
If your product only has cursor pagination, it won’t be able to support the numbered variant. Only applications with offset pagination can implement the numbered pagination variant.
Focus and move between pagination controls.
tab

Trigger button to navigate to another page.
enter

Applicable WCAG Success Criteria
This section is for reference only. This component intends to conform to the following WCAG success criteria:
-
1.3.1
Info and Relationships (Level A):
Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text. -
1.3.2
Meaningful Sequence (Level A):
When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined. -
1.4.1
Use of Color (Level A):
Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element. -
1.4.10
Reflow (Level AA):
Content can be presented without loss of information or functionality, and without requiring scrolling in two dimensions. -
1.4.11
Non-text Contrast (Level AA):
The visual presentation of the following have a contrast ratio of at least 3:1 against adjacent color(s): user interface components; graphical objects. -
1.4.12
Text Spacing (Level AA):
No loss of content or functionality occurs by setting all of the following and by changing no other style property: line height set to 1.5; spacing following paragraphs set to at least 2x the font size; letter-spacing set at least 0.12x of the font size, word spacing set to at least 0.16 times the font size. -
1.4.3
Minimum Contrast (Level AA):
The visual presentation of text and images of text has a contrast ratio of at least 4.5:1 -
2.1.1
Keyboard (Level A):
All functionality of the content is operable through a keyboard interface. -
2.1.2
No Keyboard Trap (Level A):
If keyboard focus can be moved to a component of the page using a keyboard interface, then focus can be moved away from that component using only a keyboard interface. -
2.2.1
Timing Adjustable (Level A):
If there are time limitations set by the content, one of the following should be true: turn off, adjust, extend, real-time exception, essential exception, 20 hour exception. -
2.5.3
Label in Name (Level A):
For user interface components with labels that include text or images of text, the name contains the text that is presented visually. -
4.1.2
Name, Role, Value (Level A):
For all user interface components, the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies. -
4.1.3
Status Messages (Level AA):
In content implemented using markup languages, status messages can be programmatically determined through role or properties such that they can be presented to the user by assistive technologies without receiving focus.
Support
If any accessibility issues have been found within this component, let us know by submitting an issue.
0.1.0
Første i80-version (fork af upstream, se core/UPSTREAM.md).
Related
-
Table
Used to display organized, two-dimensional tabular data.
-
Advanced Table
Used to display complex, structured tabular data with advanced features.
-
Filter patterns
Guidelines and best practices for filtering a data set using i80 components.
-
Table multi-select
Guidelines for selecting and transforming results in a Table.