Skip to content

Pagination

The Pagination component is used to navigate through a list of items that are split across multiple pages. It is a common pattern used in web applications to help users navigate through a large dataset.

<Pagination totalPages={5} currentPage={3} />

API Reference

This component’s root element is based on the <nav> element and can take in any additional HTML attributes. The following are the custom props that can be passed to the component.

Prop Type Default
totalPages *
number —
currentPage *
number —
size
"sm" | "md" | "lg" "md"

Pagination Logic

There are some core rules associated to pagination components that should be considered based on the total number of pages as well as the current page. These rules should be modified to fit your project’s use case, but in the case of this documentation the rules are as follows:

  • If there are less than 6 pages, all pages should be visible.
  • The first and last page should always be visible.
  • Show the current page and at least one page before and after it.
  • If there are 6 or more pages and the current page is more than 3 away from the start or end, ellipsis should be shown to indicate that there are more pages in between.
  • The previous and next buttons should be disabled if the current page is the first or last page, respectively.

With these rules defined, you end up getting all of the following variations:

<Pagination totalPages={5} currentPage={1} />
<Pagination totalPages={8} currentPage={3} />
<Pagination totalPages={8} currentPage={5} />
<Pagination totalPages={8} currentPage={6} />
<Pagination totalPages={5} currentPage={5} />

Accessibility

The <Pagination> component has some predefined accessibility features that are important to consider when implementing it in your project. The following are some of the key accessibility features that are included in the component:

  • The root element is given an aria-label attribute providing context to screen readers on what the element is.
  • There are sr-only elements in each of the page buttons to provide context to screen readers on what the button is for. This includes both the Previous and Next buttons as well as the page numbers themselves.

Examples

Sizes

Use the size prop to adjust the size. Pagination comes in the following different sizes: sm, md, and lg. By default, pagination will use md unless otherwise specified. Sizes should be added, removed, and tweaked as necessary based on the needs of the project.

<Pagination totalPages={5} currentPage={3} size="sm" />
<Pagination totalPages={5} currentPage={3} size="md" />
<Pagination totalPages={5} currentPage={3} size="lg" />

On Dark Backgrounds

Pagination can be used on dark backgrounds by adding the dark class to the an ancestor DOM node. THis will invert the colors of the pagination component.

<div class="dark bg-slate-900">
<Pagination totalPages={5} currentPage={3} />
</div>

Astro Component

Due to the display logic necessary for pagination, it is recommended to use it as a component. This will allow you to pass in the total number of pages and the current page as props and have the component handle the display logic for you. In this case, we have put together the following Astro component to serve as a blueprint for how you possibly handle the logic in your own project.

Pagintation.astro
---
import type { HTMLAttributes } from 'astro/types'
import Button from '@/components/elements/Button.astro'
import arrowLeft from '@/assets/svg/arrow-left.svg?raw'
import arrowRight from '@/assets/svg/arrow-right.svg?raw'
import collapseRange from '@/helpers/paginate'
interface Props extends HTMLAttributes<'nav'> {
/**
The total number of pages.
*/
totalPages: number
/**
The currently active page number.
*/
currentPage: number
/**
The size of the pagination component.
*/
size?: 'sm' | 'md' | 'lg'
}
const { totalPages, currentPage, size = 'md', ...attrs } = Astro.props
const collapsedPages = collapseRange(currentPage, totalPages)
---
<nav aria-label="pagination navigation" data-component="Pagination" {...attrs}>
<ul
class:list={[
'flex list-none flex-wrap items-center p-0',
{
'gap-8': size === 'sm',
'gap-10': size === 'md',
'gap-16': size === 'lg',
},
]}
>
<li>
<Button
variant="outlined"
href={`?page=${currentPage - 1}`}
size={size === 'lg' ? 'md' : size}
disabled={currentPage === 1}
>
<Fragment set:html={arrowLeft} />
Previous
<span class="sr-only">page</span>
</Button>
</li>
{
// show page numbers
collapsedPages.map((pageNumber) => {
if (pageNumber === '...') {
return <li class="text-sky-600 dark:text-white">...</li>
}
return (
<li>
<Button
variant={currentPage === pageNumber ? 'contained' : 'subtle'}
class:list={[
'!min-h-0 items-center justify-center rounded-full !p-0 text-center ring-inset hover:ring-1 hover:ring-sky-700 dark:hover:ring-white',
{
'size-32 text-sm': size === 'sm',
'size-44': size === 'md',
'size-56': size === 'lg',
},
]}
href={`?page=${pageNumber}`}
>
<span class="sr-only">Go to page</span>
{pageNumber}
</Button>
</li>
)
})
}
<li>
<Button
variant="outlined"
href={`?page=${currentPage + 1}`}
size={size === 'lg' ? 'md' : size}
disabled={currentPage === totalPages}
>
Next
<span class="sr-only">page</span>
<Fragment set:html={arrowRight} />
</Button>
</li>
</ul>
</nav>