Pagination
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
Pagination allows users to navigate through pages of content. It displays page numbers and navigation controls, making it easy to move between large sets of data.
Examples
Live Preview
Basic Pagination
First Last Navigation Opt-In
Bordered Style
Offset Comparison
Offset 2 (default) - shows 2 buttons on each side:
Offset 1 - shows 1 button on each side:
Alignment Options
Justify 'start', 'center', and 'end' respectively:
Custom Navigation Labels (Spanish)
Small Page Count
When there are few pages, all page numbers are shown:
Large Page Count
With many pages, ellipsis (...) indicates skipped pages:
CSS Shadow Parts Customization (Monochrome)
View Vue Code
<template>
<section>
<div class="mbe4">
<h2>Basic Pagination</h2>
</div>
<div class="stacked-mobile mbe4">
<VuePagination
:current="basicPage"
:total-pages="20"
@page-change="handleBasicPageChange"
/>
<p
v-if="basicPageMessage"
style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);"
>
{{ basicPageMessage }}
</p>
</div>
<div class="mbe4">
<h2>First Last Navigation Opt-In</h2>
</div>
<div class="stacked-mobile mbe4">
<VuePagination
:current="basicPageFirstLast"
:total-pages="20"
:first-last-navigation="true"
@page-change="handleBasicPageChangeFirstLast"
/>
<p
v-if="basicPageMessageFirstLast"
style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);"
>
{{ basicPageMessageFirstLast }}
</p>
</div>
<div class="mbe4">
<h2>Bordered Style</h2>
</div>
<div class="stacked-mobile mbe4">
<VuePagination
:current="borderedPage"
:total-pages="15"
bordered
@page-change="handleBorderedPageChange"
/>
</div>
<div class="mbe4">
<h2>Offset Comparison</h2>
</div>
<p>
Offset 2 (default) - shows 2 buttons on each side:
</p>
<div class="stacked-mobile mbe4">
<VuePagination
:current="offset2Page"
:total-pages="50"
:offset="2"
@page-change="handleOffset2PageChange"
/>
</div>
<p style="margin: 1rem 0 0.5rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
Offset 1 - shows 1 button on each side:
</p>
<div class="stacked-mobile mbe4">
<VuePagination
:current="offset1Page"
:total-pages="50"
:offset="1"
@page-change="handleOffset1PageChange"
/>
</div>
<div class="mbe4">
<h2>Alignment Options</h2>
</div>
<p>
Justify 'start', 'center', and 'end' respectively:
</p>
<div class="stacked-mobile mbe4">
<VuePagination
:current="1"
:total-pages="10"
justify="start"
/>
</div>
<div class="stacked-mobile mbe4">
<VuePagination
:current="1"
:total-pages="10"
justify="center"
/>
</div>
<div class="stacked-mobile mbe4">
<VuePagination
:current="1"
:total-pages="10"
justify="end"
/>
</div>
<div class="mbe4">
<h2>Custom Navigation Labels (Spanish)</h2>
</div>
<div class="stacked-mobile mbe4">
<VuePagination
:current="spanishPage"
:total-pages="10"
:first-last-navigation="true"
:navigation-labels="{
first: 'Primera',
previous: 'Anterior',
next: 'Siguiente',
last: 'Última'
}"
@page-change="handleSpanishPageChange"
/>
</div>
<div class="mbe4">
<h2>Small Page Count</h2>
</div>
<p>
When there are few pages, all page numbers are shown:
</p>
<div class="stacked-mobile mbe4">
<VuePagination
:current="smallPage"
:total-pages="5"
@page-change="handleSmallPageChange"
/>
</div>
<div class="mbe4">
<h2>Large Page Count</h2>
</div>
<p>
With many pages, ellipsis (...) indicates skipped pages:
</p>
<div class="stacked-mobile mbe4">
<VuePagination
:current="largePage"
:total-pages="100"
@page-change="handleLargePageChange"
/>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization (Monochrome)</h2>
</div>
<div class="stacked-mobile mbe4">
<div v-html="monochromeCustomPaginationStyles"></div>
<VuePagination
class="monochrome-custom-pagination"
:current="monochromeCustomPage"
:total-pages="10"
@page-change="handleMonochromeCustomPageChange"
/>
</div>
</section>
</template>
<script>
import { VuePagination } from "agnosticui-core/pagination/vue";
export default {
name: "PaginationExamples",
components: {
VuePagination,
},
data() {
return {
basicPageFirstLast: 5,
basicPage: 5,
basicPageMessage: null,
basicPageMessageFirstLast: null,
borderedPage: 3,
noFirstLastPage: 5,
offset2Page: 25,
offset1Page: 25,
spanishPage: 1,
smallPage: 3,
largePage: 50,
customPage: 5,
monochromeCustomPage: 5,
monochromeCustomPaginationStyles: `
<style>
.monochrome-custom-pagination::part(ag-pagination-container) {
padding: 1rem;
background: #000000;
border-radius: 12px;
}
.monochrome-custom-pagination::part(ag-pagination) {
gap: 0.25rem;
}
.monochrome-custom-pagination::part(ag-pagination-button) {
min-width: 2.5rem;
height: 2.5rem;
background: transparent;
color: #ffffff;
border: 1px solid #404040;
font-weight: 400;
transition: all 0.2s ease;
}
.monochrome-custom-pagination::part(ag-pagination-button):hover:not(:disabled) {
background: #1a1a1a;
border-color: #ffffff;
font-weight: 600;
transform: translateY(-1px);
}
.monochrome-custom-pagination .pagination-item-active .pagination-button {
background: #ffffff !important;
color: #000000 !important;
font-weight: 600;
border-color: #ffffff;
}
.monochrome-custom-pagination::part(ag-pagination-button):disabled {
background: transparent;
color: #404040;
border-color: #262626;
}
</style>
`,
};
},
methods: {
handleBasicPageChangeFirstLast(detail) {
this.basicPageFirstLast = detail.page;
this.basicPageMessageFirstLast = `Current page: ${detail.page} of 20`;
},
handleBasicPageChange(detail) {
this.basicPage = detail.page;
this.basicPageMessage = `Current page: ${detail.page} of 20`;
},
handleBorderedPageChange(detail) {
this.borderedPage = detail.page;
},
handleNoFirstLastPageChange(detail) {
this.noFirstLastPage = detail.page;
},
handleOffset2PageChange(detail) {
this.offset2Page = detail.page;
},
handleOffset1PageChange(detail) {
this.offset1Page = detail.page;
},
handleSpanishPageChange(detail) {
this.spanishPage = detail.page;
},
handleSmallPageChange(detail) {
this.smallPage = detail.page;
},
handleLargePageChange(detail) {
this.largePage = detail.page;
},
handleCustomPageChange(detail) {
this.customPage = detail.page;
},
handleMonochromeCustomPageChange(detail) {
this.monochromeCustomPage = detail.page;
},
},
};
</script>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/pagination';
export class PaginationLitExamples extends LitElement {
static properties = {
basicPageFirstLast: { type: Number },
basicPage: { type: Number },
basicPageMessage: { type: String },
basicPageMessageFirstLast: { type: String },
borderedPage: { type: Number },
offset2Page: { type: Number },
offset1Page: { type: Number },
spanishPage: { type: Number },
smallPage: { type: Number },
largePage: { type: Number },
monochromeCustomPage: { type: Number }
};
constructor() {
super();
this.basicPageFirstLast = 5;
this.basicPage = 5;
this.basicPageMessage = null;
this.basicPageMessageFirstLast = null;
this.borderedPage = 3;
this.offset2Page = 25;
this.offset1Page = 25;
this.spanishPage = 1;
this.smallPage = 3;
this.largePage = 50;
this.monochromeCustomPage = 5;
}
// Render in light DOM to access global utility classes
createRenderRoot() {
return this;
}
handleBasicPageChangeFirstLast(e) {
this.basicPageFirstLast = e.detail.page;
this.basicPageMessageFirstLast = `Current page: ${e.detail.page} of 20`;
}
handleBasicPageChange(e) {
this.basicPage = e.detail.page;
this.basicPageMessage = `Current page: ${e.detail.page} of 20`;
}
handleBorderedPageChange(e) {
this.borderedPage = e.detail.page;
}
handleOffset2PageChange(e) {
this.offset2Page = e.detail.page;
}
handleOffset1PageChange(e) {
this.offset1Page = e.detail.page;
}
handleSpanishPageChange(e) {
this.spanishPage = e.detail.page;
}
handleSmallPageChange(e) {
this.smallPage = e.detail.page;
}
handleLargePageChange(e) {
this.largePage = e.detail.page;
}
handleMonochromeCustomPageChange(e) {
this.monochromeCustomPage = e.detail.page;
}
render() {
return html`
<section>
<div class="mbe4">
<h2>Basic Pagination</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.basicPage}
total-pages="20"
@page-change=${this.handleBasicPageChange}
></ag-pagination>
${this.basicPageMessage ? html`
<p style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);">
${this.basicPageMessage}
</p>
` : ''}
</div>
<div class="mbe4">
<h2>First Last Navigation Opt-In</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.basicPageFirstLast}
total-pages="20"
first-last-navigation
@page-change=${this.handleBasicPageChangeFirstLast}
></ag-pagination>
${this.basicPageMessageFirstLast ? html`
<p style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);">
${this.basicPageMessageFirstLast}
</p>
` : ''}
</div>
<div class="mbe4">
<h2>Bordered Style</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.borderedPage}
total-pages="15"
bordered
@page-change=${this.handleBorderedPageChange}
></ag-pagination>
</div>
<div class="mbe4">
<h2>Offset Comparison</h2>
</div>
<p>
Offset 2 (default) - shows 2 buttons on each side:
</p>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.offset2Page}
total-pages="50"
offset="2"
@page-change=${this.handleOffset2PageChange}
></ag-pagination>
</div>
<p style="margin: 1rem 0 0.5rem; font-size: 0.875rem; color: var(--ag-text-secondary);">
Offset 1 - shows 1 button on each side:
</p>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.offset1Page}
total-pages="50"
offset="1"
@page-change=${this.handleOffset1PageChange}
></ag-pagination>
</div>
<div class="mbe4">
<h2>Alignment Options</h2>
</div>
<p>
Justify 'start', 'center', and 'end' respectively:
</p>
<div class="stacked-mobile mbe4">
<ag-pagination
current="1"
total-pages="10"
justify="start"
></ag-pagination>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
current="1"
total-pages="10"
justify="center"
></ag-pagination>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
current="1"
total-pages="10"
justify="end"
></ag-pagination>
</div>
<div class="mbe4">
<h2>Custom Navigation Labels (Spanish)</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.spanishPage}
total-pages="10"
first-last-navigation
navigation-labels='{"first":"Primera","previous":"Anterior","next":"Siguiente","last":"Última"}'
@page-change=${this.handleSpanishPageChange}
></ag-pagination>
</div>
<div class="mbe4">
<h2>Small Page Count</h2>
</div>
<p>
When there are few pages, all page numbers are shown:
</p>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.smallPage}
total-pages="5"
@page-change=${this.handleSmallPageChange}
></ag-pagination>
</div>
<div class="mbe4">
<h2>Large Page Count</h2>
</div>
<p>
With many pages, ellipsis (...) indicates skipped pages:
</p>
<div class="stacked-mobile mbe4">
<ag-pagination
current=${this.largePage}
total-pages="100"
@page-change=${this.handleLargePageChange}
></ag-pagination>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization (Monochrome)</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-pagination
class="monochrome-custom-pagination"
current=${this.monochromeCustomPage}
total-pages="10"
@page-change=${this.handleMonochromeCustomPageChange}
></ag-pagination>
</div>
</section>
<style>
.monochrome-custom-pagination::part(ag-pagination-container) {
padding: 1rem;
background: #000000;
border-radius: 12px;
}
.monochrome-custom-pagination::part(ag-pagination) {
gap: 0.25rem;
}
.monochrome-custom-pagination::part(ag-pagination-button) {
min-width: 2.5rem;
height: 2.5rem;
background: transparent;
color: #ffffff;
border: 1px solid #404040;
font-weight: 400;
transition: all 0.2s ease;
}
.monochrome-custom-pagination::part(ag-pagination-button):hover:not(:disabled) {
background: #1a1a1a;
border-color: #ffffff;
font-weight: 600;
transform: translateY(-1px);
}
.monochrome-custom-pagination .pagination-item-active .pagination-button {
background: #ffffff !important;
color: #000000 !important;
font-weight: 600;
border-color: #ffffff;
}
.monochrome-custom-pagination::part(ag-pagination-button):disabled {
background: transparent;
color: #404040;
border-color: #262626;
}
</style>
`;
}
}
// Register the custom element
customElements.define('pagination-lit-examples', PaginationLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState } from "react";
import { ReactPagination } from "agnosticui-core/pagination/react";
export default function PaginationReactExamples() {
const [basicPageFirstLast, setBasicPageFirstLast] = useState(5);
const [basicPage, setBasicPage] = useState(5);
const [basicPageMessage, setBasicPageMessage] = useState(null);
const [basicPageMessageFirstLast, setBasicPageMessageFirstLast] = useState(null);
const [borderedPage, setBorderedPage] = useState(3);
const [offset2Page, setOffset2Page] = useState(25);
const [offset1Page, setOffset1Page] = useState(25);
const [spanishPage, setSpanishPage] = useState(1);
const [smallPage, setSmallPage] = useState(3);
const [largePage, setLargePage] = useState(50);
const [monochromeCustomPage, setMonochromeCustomPage] = useState(5);
const handleBasicPageChangeFirstLast = (event) => {
setBasicPageFirstLast(event.detail.page);
setBasicPageMessageFirstLast(`Current page: ${event.detail.page} of 20`);
};
const handleBasicPageChange = (event) => {
setBasicPage(event.detail.page);
setBasicPageMessage(`Current page: ${event.detail.page} of 20`);
};
const handleBorderedPageChange = (event) => {
setBorderedPage(event.detail.page);
};
const handleOffset2PageChange = (event) => {
setOffset2Page(event.detail.page);
};
const handleOffset1PageChange = (event) => {
setOffset1Page(event.detail.page);
};
const handleSpanishPageChange = (event) => {
setSpanishPage(event.detail.page);
};
const handleSmallPageChange = (event) => {
setSmallPage(event.detail.page);
};
const handleLargePageChange = (event) => {
setLargePage(event.detail.page);
};
const handleMonochromeCustomPageChange = (event) => {
setMonochromeCustomPage(event.detail.page);
};
return (
<section>
<div className="mbe4">
<h2>Basic Pagination</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactPagination
current={basicPage}
totalPages={20}
onPageChange={handleBasicPageChange}
/>
{basicPageMessage && (
<p style={{ margin: 0, padding: 0, fontSize: "var(--ag-font-size-sm)", color: "var(--ag-text-secondary)" }}>
{basicPageMessage}
</p>
)}
</div>
<div className="mbe4">
<h2>First Last Navigation Opt-In</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactPagination
current={basicPageFirstLast}
totalPages={20}
firstLastNavigation={true}
onPageChange={handleBasicPageChangeFirstLast}
/>
{basicPageMessageFirstLast && (
<p style={{ margin: 0, padding: 0, fontSize: "var(--ag-font-size-sm)", color: "var(--ag-text-secondary)" }}>
{basicPageMessageFirstLast}
</p>
)}
</div>
<div className="mbe4">
<h2>Bordered Style</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactPagination
current={borderedPage}
totalPages={15}
bordered
onPageChange={handleBorderedPageChange}
/>
</div>
<div className="mbe4">
<h2>Offset Comparison</h2>
</div>
<p>Offset 2 (default) - shows 2 buttons on each side:</p>
<div className="stacked-mobile mbe4">
<ReactPagination
current={offset2Page}
totalPages={50}
offset={2}
onPageChange={handleOffset2PageChange}
/>
</div>
<p style={{ margin: "1rem 0 0.5rem", fontSize: "0.875rem", color: "var(--ag-text-secondary)" }}>
Offset 1 - shows 1 button on each side:
</p>
<div className="stacked-mobile mbe4">
<ReactPagination
current={offset1Page}
totalPages={50}
offset={1}
onPageChange={handleOffset1PageChange}
/>
</div>
<div className="mbe4">
<h2>Alignment Options</h2>
</div>
<p>Justify 'start', 'center', and 'end' respectively:</p>
<div className="stacked-mobile mbe4">
<ReactPagination current={1} totalPages={10} justify="start" />
</div>
<div className="stacked-mobile mbe4">
<ReactPagination current={1} totalPages={10} justify="center" />
</div>
<div className="stacked-mobile mbe4">
<ReactPagination current={1} totalPages={10} justify="end" />
</div>
<div className="mbe4">
<h2>Custom Navigation Labels (Spanish)</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactPagination
current={spanishPage}
totalPages={10}
firstLastNavigation={true}
navigationLabels={{
first: "Primera",
previous: "Anterior",
next: "Siguiente",
last: "Última",
}}
onPageChange={handleSpanishPageChange}
/>
</div>
<div className="mbe4">
<h2>Small Page Count</h2>
</div>
<p>When there are few pages, all page numbers are shown:</p>
<div className="stacked-mobile mbe4">
<ReactPagination
current={smallPage}
totalPages={5}
onPageChange={handleSmallPageChange}
/>
</div>
<div className="mbe4">
<h2>Large Page Count</h2>
</div>
<p>With many pages, ellipsis (...) indicates skipped pages:</p>
<div className="stacked-mobile mbe4">
<ReactPagination
current={largePage}
totalPages={100}
onPageChange={handleLargePageChange}
/>
</div>
<div className="mbe4">
<h2>CSS Shadow Parts Customization (Monochrome)</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactPagination
className="monochrome-custom-pagination"
current={monochromeCustomPage}
totalPages={10}
onPageChange={handleMonochromeCustomPageChange}
/>
</div>
</section>
);
}
Usage
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add PaginationThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Vue
<template>
<section>
<VuePagination
:current="currentPage"
:total-pages="20"
@page-change="handlePageChange"
/>
<VuePagination :current="1" :total-pages="15" bordered />
<VuePagination
:current="5"
:total-pages="10"
:first-last-navigation="true"
/>
<VuePagination :current="10" :total-pages="20" :offset="1" />
<VuePagination :current="1" :total-pages="10" justify="center" />
<VuePagination
:current="1"
:total-pages="10"
:first-last-navigation="true"
:navigation-labels="{
first: 'Primera',
previous: 'Anterior',
next: 'Siguiente',
last: 'Última',
}"
/>
</section>
</template>
<script>
import { VuePagination } from "agnosticui-core/pagination/vue";
export default {
components: {
VuePagination,
},
data() {
return {
currentPage: 5,
};
},
methods: {
handlePageChange(detail) {
this.currentPage = detail.page;
console.log(`Page changed to ${detail.page}`);
},
},
};
</script>React
import { useState } from "react";
import { ReactPagination } from "agnosticui-core/pagination/react";
export default function PaginationExample() {
const [currentPage, setCurrentPage] = useState(5);
const handlePageChange = (event) => {
setCurrentPage(event.detail.page);
console.log(`Page changed to ${event.detail.page}`);
};
return (
<section>
<ReactPagination
current={currentPage}
totalPages={20}
onPageChange={handlePageChange}
/>
<ReactPagination current={1} totalPages={15} bordered />
<ReactPagination current={5} totalPages={10} firstLastNavigation={true} />
<ReactPagination current={10} totalPages={20} offset={1} />
<ReactPagination current={1} totalPages={10} justify="center" />
<ReactPagination
current={1}
totalPages={10}
firstLastNavigation={true}
navigationLabels={{
first: "Primera",
previous: "Anterior",
next: "Siguiente",
last: "Última",
}}
/>
</section>
);
}Lit (Web Components)
import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
import 'agnosticui-core/pagination';
@customElement('pagination-example')
export class PaginationExample extends LitElement {
static styles = css`
:host {
display: block;
}
section {
display: flex;
flex-direction: column;
gap: 1rem;
}
`;
firstUpdated() {
// Set up event listener for pagination in the shadow DOM
const pagination = this.shadowRoot?.querySelector('#my-pagination');
pagination?.addEventListener('page-change', (e: Event) => {
const customEvent = e as CustomEvent;
console.log(`Page changed to ${customEvent.detail.page}`);
});
}
render() {
return html`
<section>
<ag-pagination
id="my-pagination"
current="5"
total-pages="20"
></ag-pagination>
<ag-pagination current="1" total-pages="15" bordered></ag-pagination>
<ag-pagination
current="5"
total-pages="10"
first-last-navigation="true"
></ag-pagination>
<ag-pagination current="10" total-pages="20" offset="1"></ag-pagination>
<ag-pagination current="1" total-pages="10" justify="center"></ag-pagination>
</section>
`;
}
}Note: When using pagination components within a custom element's shadow DOM, set up event listeners in the component's lifecycle (e.g., firstUpdated()) rather than using DOMContentLoaded, as document.querySelector() cannot access elements inside shadow DOM. Use this.shadowRoot.querySelector() instead.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
current | number | 1 | The currently active page (1-indexed) |
totalPages | number | 1 | Total number of pages |
offset | 1 | 2 | 2 | Number of page buttons to show on each side of the current page |
justify | 'start' | 'center' | 'end' | '' | '' | Horizontal alignment of pagination controls |
ariaLabel | string | 'pagination' | ARIA label for the navigation element |
bordered | boolean | false | Show bordered style (outline instead of solid background for active page) |
firstLastNavigation | boolean | false | Show first/last page navigation buttons |
navigationLabels | NavigationLabels | See below | Custom labels for navigation buttons |
NavigationLabels Interface
interface NavigationLabels {
first: string;
previous: string;
next: string;
last: string;
}Events
The Pagination component follows AgnosticUI v2 event conventions with dual-dispatch for the page-change custom event - you can use either addEventListener or callback props (e.g., onPageChange).
| Event | Framework | Detail | Description |
|---|---|---|---|
page-change | Vue: @page-changeReact: onPageChangeLit: @page-change or .onPageChange | { page: number, pages: (number | '...')[] } | Fired when the active page changes. |
Event Handling Examples
Vue
<template>
<VuePagination
:current="currentPage"
:total-pages="20"
@page-change="handlePageChange"
/>
</template>
<script setup>
import { ref } from "vue";
import { VuePagination } from "agnosticui-core/pagination/vue";
const currentPage = ref(1);
const handlePageChange = (detail) => {
console.log("Page changed:", detail);
currentPage.value = detail.page;
};
</script>React
import { useState } from "react";
import { ReactPagination } from "agnosticui-core/pagination/react";
export default function PaginationEventExample() {
const [currentPage, setCurrentPage] = useState(1);
const handlePageChange = (event) => {
console.log("Page changed:", event.detail);
setCurrentPage(event.detail.page);
};
return (
<ReactPagination
current={currentPage}
totalPages={20}
onPageChange={handlePageChange}
/>
);
}Lit (Web Components)
<script type="module">
import "agnosticui-core/pagination";
const pagination = document.querySelector("#my-pagination");
pagination.addEventListener("page-change", (event) => {
console.log("Page changed:", event.detail);
});
pagination.onPageChange = (event) => {
console.log("Page changed (callback):", event.detail);
};
</script>
<ag-pagination id="my-pagination" current="1" total-pages="20"></ag-pagination>Offset Behavior
The offset prop controls how many page buttons appear on each side of the current page:
Offset 2 (default)
Shows 2 buttons on each side of current page:
- Current page 10 of 50:
[1] ... [8] [9] [10] [11] [12] ... [50] - Shows 5 consecutive page numbers centered around current page
Offset 1
Shows 1 button on each side of current page:
- Current page 10 of 50:
[1] ... [9] [10] [11] ... [50] - Shows 3 consecutive page numbers centered around current page
The first and last pages are always visible unless firstLastNavigation is false.
Alignment
Use the justify prop to control horizontal alignment:
''(default): Left-aligned'start': Explicitly left-aligned'center': Centered'end': Right-aligned
Accessibility
The Pagination component implements accessible navigation:
- Uses
<nav>element witharia-labelfor screen readers - Page buttons indicate current page with
aria-current="page" - Disabled buttons (first/previous on page 1, next/last on last page) have
aria-disabled="true"and are not focusable - Each button has descriptive
aria-label(e.g., "Goto page 5", "Page 5, current page") - Navigation labels are customizable for internationalization
- Focus management automatically focuses the current page button after page changes
Best Practices
- Always provide a meaningful
ariaLabelwhen multiple pagination components exist on the same page - Keep the total number of pages reasonable (consider filtering or search for very large datasets)
- Use
offset={1}for mobile/narrow viewports to reduce visual clutter - Update the page content when
page-changeevent fires - Consider showing a loading state while fetching new page data
- Display the current page range (e.g., "Showing 11-20 of 200 items") near the pagination
Styling
Bordered Style
Set bordered={true} to use an outline style for the active page instead of a solid background:
<VuePagination :current="5" :total-pages="20" bordered />This can be useful for lighter UI themes or to reduce visual weight.
CSS Shadow Parts
| Part | Description |
|---|---|
ag-pagination-container | The outer nav container element |
ag-pagination | The pagination list (ul) element |
ag-pagination-item | Individual pagination item wrapper (li) |
ag-pagination-button | Individual pagination button |
Example
.custom-pagination::part(ag-pagination-container) {
padding: 1rem;
background: #f5f5f5;
}
.custom-pagination::part(ag-pagination) {
gap: 0.5rem;
}
.custom-pagination::part(ag-pagination-button) {
border-radius: 50%;
min-width: 2.5rem;
height: 2.5rem;
}
.custom-pagination::part(ag-pagination-button):hover {
transform: scale(1.1);
transition: transform 0.2s;
}Internationalization
Customize navigation labels for different languages:
<VuePagination
:current="1"
:total-pages="10"
:first-last-navigation="true"
:navigation-labels="{
first: '最初',
previous: '前',
next: '次',
last: '最後',
}"
/>{ first: "最初", previous: "前", next: "次", last: "最後" }{ first: "Primera", previous: "Anterior", next: "Siguiente", last: "Última" }Content Pagination
Content Pagination provides navigation between sequential content items (like documentation pages or articles). It displays previous/next links and an optional parent/overview link, making it easy to navigate through structured content hierarchies.
Examples
Live Preview
Basic Content Pagination
Without hrefs (navigate event only)
Links without href still fire navigate events:
Previous and Next Only
Omit parent link for simpler sequential navigation:
Parent Only
Show only the parent/overview link:
Bordered Style
Custom Icons
Override default icons using slots:
Only Next
First page scenario - only show next link:
Only Previous
Last page scenario - only show previous link:
CSS Shadow Parts Customization (Monochrome)
View Vue Code
<template>
<section>
<div class="mbe4">
<h2>Basic Content Pagination</h2>
</div>
<div class="stacked-mobile mbe4">
<VueContentPagination
:previous="{ title: 'Introduction', href: '#examples-1' }"
:next="{ title: 'Getting Started', href: '#examples-1' }"
:parent="{ title: 'Documentation', href: '#examples-1' }"
@navigate="handleNavigate"
/>
<p
v-if="navigationMessage"
style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);"
>
{{ navigationMessage }}
</p>
</div>
<div class="mbe4">
<h2>Without hrefs (navigate event only)</h2>
</div>
<p>
Links without href still fire navigate events:
</p>
<div class="stacked-mobile mbe4">
<VueContentPagination
:previous="{ title: 'Introduction' }"
:next="{ title: 'Getting Started' }"
:parent="{ title: 'Documentation' }"
@navigate="handleNavigateNoHref"
/>
<p
v-if="navigationMessageNoHref"
style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);"
>
{{ navigationMessageNoHref }}
</p>
</div>
<div class="mbe4">
<h2>Previous and Next Only</h2>
</div>
<p>
Omit parent link for simpler sequential navigation:
</p>
<div class="stacked-mobile mbe4">
<VueContentPagination
:previous="{ title: 'Chapter 1: Basics', href: '#examples-1' }"
:next="{ title: 'Chapter 3: Advanced', href: '#examples-1' }"
/>
</div>
<div class="mbe4">
<h2>Parent Only</h2>
</div>
<p>
Show only the parent/overview link:
</p>
<div class="stacked-mobile mbe4">
<VueContentPagination :parent="{ title: 'Back to Documentation', href: '#examples-1' }" />
</div>
<div class="mbe4">
<h2>Bordered Style</h2>
</div>
<div class="stacked-mobile mbe4">
<VueContentPagination
:previous="{ title: 'Installation', href: '#examples-1' }"
:next="{ title: 'Configuration', href: '#examples-1' }"
:parent="{ title: 'Guides', href: '#examples-1' }"
bordered
/>
</div>
<div class="mbe4">
<h2>Custom Icons</h2>
</div>
<p>
Override default icons using slots:
</p>
<div class="stacked-mobile mbe4">
<VueContentPagination
:previous="{ title: 'Prev Page', href: '#examples-1' }"
:next="{ title: 'Next Page', href: '#examples-1' }"
:parent="{ title: 'Overview', href: '#examples-1' }"
>
<template #previous-icon>
<ChevronLeft :size="20" />
</template>
<template #next-icon>
<ChevronRight :size="20" />
</template>
<template #parent-icon>
<ChevronUp :size="20" />
</template>
</VueContentPagination>
</div>
<div class="mbe4">
<h2>Only Next</h2>
</div>
<p>
First page scenario - only show next link:
</p>
<div class="stacked-mobile mbe4">
<VueContentPagination
:next="{ title: 'Getting Started', href: '#examples-1' }"
:parent="{ title: 'Documentation', href: '#examples-1' }"
/>
</div>
<div class="mbe4">
<h2>Only Previous</h2>
</div>
<p>
Last page scenario - only show previous link:
</p>
<div class="stacked-mobile mbe4">
<VueContentPagination
:previous="{ title: 'Deployment', href: '#examples-1' }"
:parent="{ title: 'Documentation', href: '#examples-1' }"
/>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization (Monochrome)</h2>
</div>
<div class="stacked-mobile mbe4">
<div v-html="monochromeCustomContentPaginationStyles"></div>
<VueContentPagination
class="monochrome-custom-content-pagination"
:previous="{ title: 'Introduction', href: '#examples-1' }"
:next="{ title: 'Getting Started', href: '#examples-1' }"
:parent="{ title: 'Documentation', href: '#examples-1' }"
>
<template #previous-icon>
<ChevronLeft :size="20" />
</template>
<template #next-icon>
<ChevronRight :size="20" />
</template>
<template #parent-icon>
<ChevronUp :size="20" />
</template>
</VueContentPagination>
</div>
</section>
</template>
<script>
import { VueContentPagination } from "agnosticui-core/content-pagination/vue";
import { ChevronLeft, ChevronRight, ChevronUp } from "lucide-vue-next";
export default {
name: "ContentPaginationExamples",
components: {
VueContentPagination,
ChevronLeft,
ChevronRight,
ChevronUp,
},
data() {
return {
navigationMessage: null,
navigationMessageNoHref: null,
monochromeCustomContentPaginationStyles: `
<style>
.monochrome-custom-content-pagination::part(ag-content-pagination-container) {
padding: 1.5rem;
background: #000000;
border-radius: 12px;
}
.monochrome-custom-content-pagination::part(ag-content-pagination-parent) {
border-radius: 8px;
transition: all 0.2s ease;
padding: 0;
}
.monochrome-custom-content-pagination::part(ag-content-pagination-parent):hover {
font-weight: 700;
text-decoration: underline;
text-underline-offset: 3px;
transform: translateY(-1px);
}
.monochrome-custom-content-pagination::part(ag-content-pagination-link) {
padding: var(--ag-space-3) var(--ag-space-4);
background: transparent;
color: #ffffff;
border-radius: 8px;
transition: all 0.2s ease;
font-weight: 400;
}
.monochrome-custom-content-pagination::part(ag-content-pagination-link):hover {
background: #1a1a1a;
font-weight: 600;
text-decoration: underline;
text-underline-offset: 3px;
transform: translateY(-2px);
}
</style>
`,
};
},
methods: {
handleNavigate(detail) {
this.navigationMessage = `Clicked ${detail.type}: "${detail.item.title}"${
detail.item.href ? ` → ${detail.item.href}` : " (no href)"
}`;
// In a real app, you might do: this.$router.push(detail.item.href)
console.log("Navigate:", detail);
},
handleNavigateNoHref(detail) {
this.navigationMessageNoHref = `Clicked ${detail.type}: "${detail.item.title}" (no href provided)`;
// Custom navigation logic here
console.log("Navigate (no href):", detail);
},
},
};
</script>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/content-pagination';
export class ContentPaginationLitExamples extends LitElement {
static properties = {
navigationMessage: { type: String },
navigationMessageNoHref: { type: String }
};
constructor() {
super();
this.navigationMessage = null;
this.navigationMessageNoHref = null;
}
// Render in light DOM to access global utility classes
createRenderRoot() {
return this;
}
handleNavigate(e) {
this.navigationMessage = `Clicked ${e.detail.type}: "${e.detail.item.title}"${
e.detail.item.href ? ` → ${e.detail.item.href}` : " (no href)"
}`;
console.log("Navigate:", e.detail);
}
handleNavigateNoHref(e) {
this.navigationMessageNoHref = `Clicked ${e.detail.type}: "${e.detail.item.title}" (no href provided)`;
console.log("Navigate (no href):", e.detail);
}
render() {
return html`
<section>
<div class="mbe4">
<h2>Basic Content Pagination</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-content-pagination
previous='{"title":"Introduction","href":"#examples-1"}'
next='{"title":"Getting Started","href":"#examples-1"}'
parent='{"title":"Documentation","href":"#examples-1"}'
@navigate=${this.handleNavigate}
></ag-content-pagination>
${this.navigationMessage ? html`
<p style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);">
${this.navigationMessage}
</p>
` : ''}
</div>
<div class="mbe4">
<h2>Without hrefs (navigate event only)</h2>
</div>
<p>
Links without href still fire navigate events:
</p>
<div class="stacked-mobile mbe4">
<ag-content-pagination
previous='{"title":"Introduction"}'
next='{"title":"Getting Started"}'
parent='{"title":"Documentation"}'
@navigate=${this.handleNavigateNoHref}
></ag-content-pagination>
${this.navigationMessageNoHref ? html`
<p style="margin: 0; padding: 0; font-size: var(--ag-font-size-sm); color: var(--ag-text-secondary);">
${this.navigationMessageNoHref}
</p>
` : ''}
</div>
<div class="mbe4">
<h2>Previous and Next Only</h2>
</div>
<p>
Omit parent link for simpler sequential navigation:
</p>
<div class="stacked-mobile mbe4">
<ag-content-pagination
previous='{"title":"Chapter 1: Basics","href":"#examples-1"}'
next='{"title":"Chapter 3: Advanced","href":"#examples-1"}'
></ag-content-pagination>
</div>
<div class="mbe4">
<h2>Parent Only</h2>
</div>
<p>
Show only the parent/overview link:
</p>
<div class="stacked-mobile mbe4">
<ag-content-pagination
parent='{"title":"Back to Documentation","href":"#examples-1"}'
></ag-content-pagination>
</div>
<div class="mbe4">
<h2>Bordered Style</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-content-pagination
previous='{"title":"Installation","href":"#examples-1"}'
next='{"title":"Configuration","href":"#examples-1"}'
parent='{"title":"Guides","href":"#examples-1"}'
bordered
></ag-content-pagination>
</div>
<div class="mbe4">
<h2>Custom Icons</h2>
</div>
<p>
Override default icons using slots:
</p>
<div class="stacked-mobile mbe4">
<ag-content-pagination
previous='{"title":"Prev Page","href":"#examples-1"}'
next='{"title":"Next Page","href":"#examples-1"}'
parent='{"title":"Overview","href":"#examples-1"}'
>
<span slot="previous-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</span>
<span slot="next-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</span>
<span slot="parent-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</span>
</ag-content-pagination>
</div>
<div class="mbe4">
<h2>Only Next</h2>
</div>
<p>
First page scenario - only show next link:
</p>
<div class="stacked-mobile mbe4">
<ag-content-pagination
next='{"title":"Getting Started","href":"#examples-1"}'
parent='{"title":"Documentation","href":"#examples-1"}'
></ag-content-pagination>
</div>
<div class="mbe4">
<h2>Only Previous</h2>
</div>
<p>
Last page scenario - only show previous link:
</p>
<div class="stacked-mobile mbe4">
<ag-content-pagination
previous='{"title":"Deployment","href":"#examples-1"}'
parent='{"title":"Documentation","href":"#examples-1"}'
></ag-content-pagination>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization (Monochrome)</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-content-pagination
class="monochrome-custom-content-pagination"
previous='{"title":"Introduction","href":"#examples-1"}'
next='{"title":"Getting Started","href":"#examples-1"}'
parent='{"title":"Documentation","href":"#examples-1"}'
>
<span slot="previous-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</span>
<span slot="next-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</span>
<span slot="parent-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</span>
</ag-content-pagination>
</div>
</section>
<style>
.monochrome-custom-content-pagination::part(ag-content-pagination-container) {
padding: 1.5rem;
background: #000000;
border-radius: 12px;
}
.monochrome-custom-content-pagination::part(ag-content-pagination-parent) {
border-radius: 8px;
transition: all 0.2s ease;
padding: 0;
}
.monochrome-custom-content-pagination::part(ag-content-pagination-parent):hover {
font-weight: 700;
text-decoration: underline;
text-underline-offset: 3px;
transform: translateY(-1px);
}
.monochrome-custom-content-pagination::part(ag-content-pagination-link) {
padding: var(--ag-space-3) var(--ag-space-4);
background: transparent;
color: #ffffff;
border-radius: 8px;
transition: all 0.2s ease;
font-weight: 400;
}
.monochrome-custom-content-pagination::part(ag-content-pagination-link):hover {
background: #1a1a1a;
font-weight: 600;
text-decoration: underline;
text-underline-offset: 3px;
transform: translateY(-2px);
}
</style>
`;
}
}
// Register the custom element
customElements.define('content-pagination-lit-examples', ContentPaginationLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { useState } from "react";
import { ReactContentPagination } from "agnosticui-core/content-pagination/react";
// Simple SVG icon components
const ChevronLeft = ({ size = 20 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
);
const ChevronRight = ({ size = 20 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
);
const ChevronUp = ({ size = 20 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
);
export default function ContentPaginationReactExamples() {
const [navigationMessage, setNavigationMessage] = useState(null);
const [navigationMessageNoHref, setNavigationMessageNoHref] = useState(null);
const handleNavigate = (event) => {
setNavigationMessage(
`Clicked ${event.detail.type}: "${event.detail.item.title}"${
event.detail.item.href ? ` → ${event.detail.item.href}` : " (no href)"
}`
);
console.log("Navigate:", event.detail);
};
const handleNavigateNoHref = (event) => {
setNavigationMessageNoHref(
`Clicked ${event.detail.type}: "${event.detail.item.title}" (no href provided)`
);
console.log("Navigate (no href):", event.detail);
};
return (
<section>
<div className="mbe4">
<h2>Basic Content Pagination</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactContentPagination
previous={{ title: "Introduction", href: "#examples-1" }}
next={{ title: "Getting Started", href: "#examples-1" }}
parent={{ title: "Documentation", href: "#examples-1" }}
onNavigate={handleNavigate}
/>
{navigationMessage && (
<p style={{ margin: 0, padding: 0, fontSize: "var(--ag-font-size-sm)", color: "var(--ag-text-secondary)" }}>
{navigationMessage}
</p>
)}
</div>
<div className="mbe4">
<h2>Without hrefs (navigate event only)</h2>
</div>
<p>Links without href still fire navigate events:</p>
<div className="stacked-mobile mbe4">
<ReactContentPagination
previous={{ title: "Introduction" }}
next={{ title: "Getting Started" }}
parent={{ title: "Documentation" }}
onNavigate={handleNavigateNoHref}
/>
{navigationMessageNoHref && (
<p style={{ margin: 0, padding: 0, fontSize: "var(--ag-font-size-sm)", color: "var(--ag-text-secondary)" }}>
{navigationMessageNoHref}
</p>
)}
</div>
<div className="mbe4">
<h2>Previous and Next Only</h2>
</div>
<p>Omit parent link for simpler sequential navigation:</p>
<div className="stacked-mobile mbe4">
<ReactContentPagination
previous={{ title: "Chapter 1: Basics", href: "#examples-1" }}
next={{ title: "Chapter 3: Advanced", href: "#examples-1" }}
/>
</div>
<div className="mbe4">
<h2>Parent Only</h2>
</div>
<p>Show only the parent/overview link:</p>
<div className="stacked-mobile mbe4">
<ReactContentPagination
parent={{ title: "Back to Documentation", href: "#examples-1" }}
/>
</div>
<div className="mbe4">
<h2>Bordered Style</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactContentPagination
previous={{ title: "Installation", href: "#examples-1" }}
next={{ title: "Configuration", href: "#examples-1" }}
parent={{ title: "Guides", href: "#examples-1" }}
bordered
/>
</div>
<div className="mbe4">
<h2>Custom Icons</h2>
</div>
<p>Override default icons using slots:</p>
<div className="stacked-mobile mbe4">
<ReactContentPagination
previous={{ title: "Prev Page", href: "#examples-1" }}
next={{ title: "Next Page", href: "#examples-1" }}
parent={{ title: "Overview", href: "#examples-1" }}
>
<ChevronLeft size={20} slot="previous-icon" />
<ChevronRight size={20} slot="next-icon" />
<ChevronUp size={20} slot="parent-icon" />
</ReactContentPagination>
</div>
<div className="mbe4">
<h2>Only Next</h2>
</div>
<p>First page scenario - only show next link:</p>
<div className="stacked-mobile mbe4">
<ReactContentPagination
next={{ title: "Getting Started", href: "#examples-1" }}
parent={{ title: "Documentation", href: "#examples-1" }}
/>
</div>
<div className="mbe4">
<h2>Only Previous</h2>
</div>
<p>Last page scenario - only show previous link:</p>
<div className="stacked-mobile mbe4">
<ReactContentPagination
previous={{ title: "Deployment", href: "#examples-1" }}
parent={{ title: "Documentation", href: "#examples-1" }}
/>
</div>
<div className="mbe4">
<h2>CSS Shadow Parts Customization (Monochrome)</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactContentPagination
className="monochrome-custom-content-pagination"
previous={{ title: "Introduction", href: "#examples-1" }}
next={{ title: "Getting Started", href: "#examples-1" }}
parent={{ title: "Documentation", href: "#examples-1" }}
>
<ChevronLeft size={20} slot="previous-icon" />
<ChevronRight size={20} slot="next-icon" />
<ChevronUp size={20} slot="parent-icon" />
</ReactContentPagination>
</div>
</section>
);
}
Usage
TIP
The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add PaginationThe CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.
Vue
<template>
<section>
<VueContentPagination
:previous="{ title: 'Introduction', href: '/introduction' }"
:next="{ title: 'Getting Started', href: '/getting-started' }"
:parent="{ title: 'Documentation', href: '/documentation' }"
@navigate="handleNavigate"
/>
<VueContentPagination
:previous="{ title: 'Installation' }"
:next="{ title: 'Configuration' }"
/>
<VueContentPagination
:previous="{ title: 'Chapter 1', href: '/chapter-1' }"
:next="{ title: 'Chapter 3', href: '/chapter-3' }"
bordered
/>
<VueContentPagination
:previous="{ title: 'Prev Page' }"
:next="{ title: 'Next Page' }"
>
<template #previous-icon><ChevronLeft :size="20" /></template>
<template #next-icon><ChevronRight :size="20" /></template>
<template #parent-icon><ChevronUp :size="20" /></template>
</VueContentPagination>
</section>
</template>
<script>
import { VueContentPagination } from "agnosticui-core/content-pagination/vue";
import { ChevronLeft, ChevronRight, ChevronUp } from "lucide-vue-next";
export default {
components: {
VueContentPagination,
ChevronLeft,
ChevronRight,
ChevronUp,
},
methods: {
handleNavigate(detail) {
console.log("Navigate to:", detail);
},
},
};
</script>React
import { ReactContentPagination } from "agnosticui-core/content-pagination/react";
export default function ContentPaginationExample() {
const handleNavigate = (event) => {
console.log("Navigate to:", event.detail);
};
return (
<>
<ReactContentPagination
previous={{ title: "Introduction", href: "/introduction" }}
next={{ title: "Getting Started", href: "/getting-started" }}
parent={{ title: "Documentation", href: "/documentation" }}
onNavigate={handleNavigate}
/>
<ReactContentPagination
previous={{ title: "Chapter 1", href: "/chapter-1" }}
next={{ title: "Chapter 3", href: "/chapter-3" }}
bordered
/>
</>
);
}Lit (Web Components)
import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
import 'agnosticui-core/content-pagination';
@customElement('content-pagination-example')
export class ContentPaginationExample extends LitElement {
static styles = css`
:host {
display: block;
}
section {
display: flex;
flex-direction: column;
gap: 1rem;
}
`;
firstUpdated() {
// Set up event listener for content pagination in the shadow DOM
const contentPagination = this.shadowRoot?.querySelector('#my-content-pagination');
contentPagination?.addEventListener('navigate', (e: Event) => {
const customEvent = e as CustomEvent;
console.log('Navigate to:', customEvent.detail);
});
}
render() {
return html`
<section>
<ag-content-pagination
id="my-content-pagination"
previous='{"title": "Introduction", "href": "/introduction"}'
next='{"title": "Getting Started", "href": "/getting-started"}'
parent='{"title": "Documentation", "href": "/documentation"}'
></ag-content-pagination>
<ag-content-pagination
previous='{"title": "Chapter 1", "href": "/chapter-1"}'
next='{"title": "Chapter 3", "href": "/chapter-3"}'
bordered
></ag-content-pagination>
<ag-content-pagination
previous='{"title": "Prev Page"}'
next='{"title": "Next Page"}'
>
<span slot="previous-icon">←</span>
<span slot="next-icon">→</span>
<span slot="parent-icon">↑</span>
</ag-content-pagination>
</section>
`;
}
}Note: When using content-pagination components within a custom element's shadow DOM, set up event listeners in the component's lifecycle (e.g., firstUpdated()) rather than using DOMContentLoaded, as document.querySelector() cannot access elements inside shadow DOM. Use this.shadowRoot.querySelector() instead.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
previous | ContentItem | undefined | Previous content item |
next | ContentItem | undefined | Next content item |
parent | ContentItem | undefined | Parent/overview content item |
ariaLabel | string | 'Content navigation' | ARIA label for the navigation element |
bordered | boolean | false | Show bordered style around navigation links |
ContentItem Interface
interface ContentItem {
title: string;
href?: string;
}Events
The Content Pagination component follows AgnosticUI v2 event conventions with dual-dispatch for the navigate custom event.
| Event | Framework | Detail | Description |
|---|---|---|---|
navigate | Vue: @navigateReact: onNavigateLit: @navigate or .onNavigate | { type: 'previous' | 'next' | 'parent', item: ContentItem } | Fired when user clicks a navigation link |
Event Handling Examples
Vue
<template>
<VueContentPagination
:previous="previous"
:next="next"
:parent="parent"
@navigate="handleNavigate"
/>
</template>
<script setup>
import { VueContentPagination } from "agnosticui-core/content-pagination/vue";
import { useRouter } from "vue-router";
const router = useRouter();
const previous = { title: "Introduction", href: "/docs/introduction" };
const next = { title: "Getting Started", href: "/docs/getting-started" };
const parent = { title: "Documentation", href: "/docs" };
const handleNavigate = (detail) => {
console.log("Navigate:", detail);
if (detail.item.href) {
router.push(detail.item.href);
}
};
</script>React
import { ReactContentPagination } from "agnosticui-core/content-pagination/react";
import { useNavigate } from "react-router-dom";
export default function ContentPaginationEventExample() {
const navigate = useNavigate();
const handleNavigate = (event) => {
console.log("Navigate:", event.detail);
if (event.detail.item.href) {
navigate(event.detail.item.href);
}
};
return (
<ReactContentPagination
previous={{ title: "Introduction", href: "/docs/introduction" }}
next={{ title: "Getting Started", href: "/docs/getting-started" }}
parent={{ title: "Documentation", href: "/docs" }}
onNavigate={handleNavigate}
/>
);
}Lit (Web Components)
import "agnosticui-core/content-pagination";
const contentPagination = document.querySelector("#my-content-pagination");
contentPagination.addEventListener("navigate", (event) => {
console.log("Navigate:", event.detail);
if (event.detail.href) {
window.location.href = event.detail.href;
}
});
contentPagination.onNavigate = (event) => {
console.log("Navigate (callback):", event.detail);
};Slots/Children
Content Pagination provides slots for customizing navigation icons:
| Slot/Template | Description |
|---|---|
previous-icon | Custom icon for the previous link |
next-icon | Custom icon for the next link |
parent-icon | Custom icon for the parent link |
Custom Icons Example
Vue
<template>
<VueContentPagination :previous="previous" :next="next" :parent="parent">
<template #previous-icon><ChevronLeft :size="20" /></template>
<template #next-icon><ChevronRight :size="20" /></template>
<template #parent-icon><ChevronUp :size="20" /></template>
</VueContentPagination>
</template>Accessibility
The Content Pagination component implements accessible navigation:
- Uses
<nav>element witharia-labelfor screen readers - Each link has descriptive text (title) for clarity
- Links without
hrefare rendered as buttons with appropriate semantics - Keyboard navigation works seamlessly (Tab, Enter/Space)
Best Practices
- Provide descriptive titles that clearly indicate the destination
- Include
hrefwhen possible for standard link behavior and SEO - Use the
navigateevent to integrate with client-side routing - Position content pagination at the end of your content for natural flow
- Consider showing content pagination at both top and bottom for long content
Styling
Bordered Style
Set bordered={true} to display borders around navigation links:
<VueContentPagination :previous="previous" :next="next" bordered />CSS Shadow Parts
| Part | Description |
|---|---|
ag-content-pagination-container | The outer nav container element |
ag-content-pagination-parent | The parent/overview link |
ag-content-pagination-previous | The previous link container |
ag-content-pagination-next | The next link container |
ag-content-pagination-link | Individual navigation link/button |
Example
.custom-content-pagination::part(ag-content-pagination-container) {
padding: 2rem 1rem;
background: #f9fafb;
border-radius: 8px;
}
.custom-content-pagination::part(ag-content-pagination-parent) {
margin-bottom: 1.5rem;
}
.custom-content-pagination::part(ag-content-pagination-link) {
padding: 1rem 1.5rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
transition: all 0.2s;
}
.custom-content-pagination::part(ag-content-pagination-link):hover {
background-color: #dbeafe;
border-color: #3b82f6;
transform: translateY(-2px);
}Use Cases
Content Pagination is ideal for:
- Documentation sites: Navigate between guide pages, API references, and tutorials
- Multi-page articles: Move between chapters or sections of long-form content
- Course materials: Progress through lessons with easy navigation to course overview
- Blog series: Link related posts in a series with navigation to the series index
- Wizard flows: Guide users through multi-step processes with clear navigation