Skip to content

Pagination

Experimental Alpha

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

Vue
Lit
React
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>
  );
}
Open in StackBlitz

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:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Pagination

The 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
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
tsx
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)
typescript
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

PropTypeDefaultDescription
currentnumber1The currently active page (1-indexed)
totalPagesnumber1Total number of pages
offset1 | 22Number of page buttons to show on each side of the current page
justify'start' | 'center' | 'end' | ''''Horizontal alignment of pagination controls
ariaLabelstring'pagination'ARIA label for the navigation element
borderedbooleanfalseShow bordered style (outline instead of solid background for active page)
firstLastNavigationbooleanfalseShow first/last page navigation buttons
navigationLabelsNavigationLabelsSee belowCustom labels for navigation buttons
typescript
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).

EventFrameworkDetailDescription
page-changeVue: @page-change
React: onPageChange
Lit: @page-change or .onPageChange
{ page: number, pages: (number | '...')[] }Fired when the active page changes.

Event Handling Examples

Vue
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
tsx
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)
html
<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 with aria-label for 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 ariaLabel when 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-change event 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:

vue
<VuePagination :current="5" :total-pages="20" bordered />

This can be useful for lighter UI themes or to reduce visual weight.

CSS Shadow Parts

PartDescription
ag-pagination-containerThe outer nav container element
ag-paginationThe pagination list (ul) element
ag-pagination-itemIndividual pagination item wrapper (li)
ag-pagination-buttonIndividual pagination button

Example

css
.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:

vue
<VuePagination
  :current="1"
  :total-pages="10"
  :first-last-navigation="true"
  :navigation-labels="{
    first: '最初',
    previous: '前',
    next: '次',
    last: '最後',
  }"
/>
javascript
{ first: "最初", previous: "前", next: "次", last: "最後" }
javascript
{ 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

Vue
Lit
React
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>
  );
}
Open in StackBlitz

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:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Pagination

The 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
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
tsx
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)
typescript
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

PropTypeDefaultDescription
previousContentItemundefinedPrevious content item
nextContentItemundefinedNext content item
parentContentItemundefinedParent/overview content item
ariaLabelstring'Content navigation'ARIA label for the navigation element
borderedbooleanfalseShow bordered style around navigation links

ContentItem Interface

typescript
interface ContentItem {
  title: string;
  href?: string;
}

Events

The Content Pagination component follows AgnosticUI v2 event conventions with dual-dispatch for the navigate custom event.

EventFrameworkDetailDescription
navigateVue: @navigate
React: onNavigate
Lit: @navigate or .onNavigate
{ type: 'previous' | 'next' | 'parent', item: ContentItem }Fired when user clicks a navigation link

Event Handling Examples

Vue
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
tsx
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)
lit

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/TemplateDescription
previous-iconCustom icon for the previous link
next-iconCustom icon for the next link
parent-iconCustom icon for the parent link

Custom Icons Example

Vue
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 with aria-label for screen readers
  • Each link has descriptive text (title) for clarity
  • Links without href are rendered as buttons with appropriate semantics
  • Keyboard navigation works seamlessly (Tab, Enter/Space)

Best Practices

  • Provide descriptive titles that clearly indicate the destination
  • Include href when possible for standard link behavior and SEO
  • Use the navigate event 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:

vue
<VueContentPagination :previous="previous" :next="next" bordered />

CSS Shadow Parts

PartDescription
ag-content-pagination-containerThe outer nav container element
ag-content-pagination-parentThe parent/overview link
ag-content-pagination-previousThe previous link container
ag-content-pagination-nextThe next link container
ag-content-pagination-linkIndividual navigation link/button

Example

css
.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