Skip to content

Pagination

Edit on GitHub

Navigate through pages of content with dots, numbers, or page controls.

@volvo-cars/css v2.5.1@volvo-cars/react-headless v0.25.1

Navigate through paginated data with numbered buttons and prev/next arrows. Use usePagination to generate the page list.

volvocars.com
Show codeHide code
import { type PaginationItem, usePagination } from '@volvo-cars/react-headless';
import { Icon, IconButton } from '@volvo-cars/react-icons';
export function PagePagination() {
const { setPage, pages, previousButtonProps, nextButtonProps } =
usePagination({ totalPages: 10, initialPage: 5 });
return (
<nav aria-label="pagination" className="flex-row gap-8">
<ul style={{ display: 'contents', listStyle: 'none' }}>
<li>
<IconButton
{...previousButtonProps}
icon="chevron-back"
aria-label="Go to previous page"
variant="clear"
/>
</li>
{pages.map((page: PaginationItem) => {
if (page.type === 'ELLIPSIS') {
return (
<li
key={page.type}
className="w-40 flex-row items-end justify-center"
>
<Icon icon="three-dots" size={16} alt="Hidden pages" />
</li>
);
}
if (page.type === 'ELLIPSIS_SMALL') {
return (
<li
key={page.type}
className="w-40 flex-row items-end justify-center lg:hidden"
>
<Icon icon="three-dots" size={16} alt="Hidden pages" />
</li>
);
}
const pageNumber = page.value;
if (pageNumber === undefined) return null;
return (
<li
key={pageNumber}
className={page.isAdditional ? 'until-lg:hidden' : ''}
>
<button
type="button"
aria-label={`Go to page ${pageNumber}`}
aria-current={page.isActive ? 'page' : undefined}
className="icon-button-clear current:bg-surface-neutral current:text-inverted"
onClick={() => setPage(pageNumber)}
>
{pageNumber}
</button>
</li>
);
})}
<li>
<IconButton
{...nextButtonProps}
icon="chevron-forward"
aria-label="Go to next page"
variant="clear"
/>
</li>
</ul>
</nav>
);
}

Use dots for carousels where each dot represents a single visible item. Best for 5 or fewer items.

Show codeHide code
<ul class="pagination-dots">
<li>
<button type="button" aria-current="true">
<span class="sr-only">Go to slide 1</span>
</button>
</li>
<li>
<button type="button">
<span class="sr-only">Go to slide 2</span>
</button>
</li>
<li>
<button type="button">
<span class="sr-only">Go to slide 3</span>
</button>
</li>
<li>
<button type="button">
<span class="sr-only">Go to slide 4</span>
</button>
</li>
<li>
<button type="button">
<span class="sr-only">Go to slide 5</span>
</button>
</li>
</ul>

Apply the pagination-dots class to a container of buttons. Mark the active dot with aria-current="true".

<div class="pagination-dots">
<button aria-current="true"><span class="sr-only">Slide 1</span></button>
<button><span class="sr-only">Slide 2</span></button>
<button><span class="sr-only">Slide 3</span></button>
</div>

Combine with useSnapIndicators and useSnapNavigation for a complete carousel.


Display position as text (e.g., “3 / 10”) for slideshows or galleries where the total count matters.

Use activeIndex from useSnapIndicators to show the current position and indicators.length for the total.

Combine pagination dots with an absolute-positioned pill-filled overlay showing the numeric position.


  • Container label: add aria-label="Pagination" to the <nav> wrapper
  • Active indicator: use aria-current="page" (page numbers) or aria-current="true" (dots)
  • Button labels: provide descriptive text, e.g., aria-label="Go to page 5" or use sr-only spans for dots
  • Keyboard: ensure Tab navigates between buttons and Enter/Space activates them