Hybrid Astro UI

PaginationControls

Open in ChatGPT

A compact, Astro-native pagination system that syncs directly with Astro’s routing and pagination APIs, offering modular controls, dynamic page indices, and zero client-side JavaScript. Ideal for clean, scalable multi-page navigation in Astro projects.

Install

pnpm dlx hybrid-astro-ui@latest add pagination-controls
npx hybrid-astro-ui@latest add pagination-controls
yarn dlx hybrid-astro-ui@latest add pagination-controls
bunx hybrid-astro-ui@latest add pagination-controls

Usage

---
import { Pagination, 
  PaginationButton, 
  PaginationIndex, 
  PaginationIndexContainer 
  } from "@/src/components/hybrid-astro-u/pagination-controls"
---
<Pagination>
    <PaginationButton path={undefined}> Prev </PaginationButton>
    <PaginationIndexContainer>
      <PaginationIndex
            path="/components/1"
            active={false}
            label={1}
      />
      <PaginationIndex
         path="/components/3"
          active={true}
          label={2}
      />
      <PaginationIndex
        path="/components/3"
        active={false}
        label={3}
      />
    </PaginationIndexContainer>
    <PaginationButton path="/components/2"> Next </PaginationButton>
</Pagination>

Real case

The following example demonstrates how to integrate the pagination system into a real page. The first step is to create a component that wraps all the logic required to render the pagination controls. This component handles generating page links, calculating the visible range, and rendering the “Prev” and “Next” buttons.

---
// components/Controls.astro
import { type Page } from "astro";
import { Pagination, 
  PaginationButton, 
  PaginationIndex, 
  PaginationIndexContainer 
  } from "@/src/components/hybrid-astro-u/pagination-controls"
---

interface Props {
    paginationInfo: Page;
}
const page_index = Astro.params;
const { paginationInfo } = Astro.props as Props;

const pages = Array.from({ length: 5 }, (_, i) => {
    if (paginationInfo.currentPage + i > paginationInfo.lastPage) {
        return null;
    }

    return {
        href: `/components/${paginationInfo.currentPage + i}`,
        label: paginationInfo.currentPage + i,
    };
}).filter((v) => v !== null);
---

<Pagination>
    <PaginationButton path={paginationInfo.url.prev}> Prev </PaginationButton>

    <PaginationIndexContainer>
        {
            pages.map((p) => (
                <PaginationIndex
                    path={p.href}
                    active={parseInt(page_index.page || "1") === p.label}
                    label={p.label}
                />
            ))
        }
    </PaginationIndexContainer>

    <PaginationButton path={paginationInfo.url.next}> Next </PaginationButton>
</Pagination>

In this example, the Controls component builds a dynamic list of pages based on the paginationInfo passed from the parent. The pages array automatically excludes invalid pages, ensuring that no out-of-range links are rendered. Each item is then passed to PaginationIndex, which highlights the active page based on the current URL parameters.

Once the Controls component is ready, you can import and use it directly inside any page that requires pagination. Here, we generate static paths using createCollectionPagination, which creates individual routes for each page of the collection.

---
//   components/[page].astro
import { createCollectionPagination } from "@/src/lib/utils";
import Controls from "......."


export const getStaticPaths = createCollectionPagination({
    collection: "components",
    pageSize: 3,
    sortByFn: (a, b) => {
        const titleA = a.data.title.toUpperCase();
        const titleB = b.data.title.toUpperCase();

        if (titleA < titleB) {
            return -1;
        }
        if (titleA > titleB) {
            return 1;
        }
        return 0;
    },
    filterFn: (item) => item.data.category === "components",
});

const { page } = Astro.props;
---
<section class="w-full flex flex-col items-center justify-center py-20">
    <div class="flex flex-col gap-2 prose dark:prose-invert items-center">
        {
            page.data.map((item) => (
                <div>
                    <h2>{item.data.title}</h2>
                    <p>{item.data.description}</p>
                </div>
            ))
        }

        <Controls paginationInfo={page} />
     
    </div>
</section>

Inside the template, we iterate through page.data to display the items that belong to the current page. Finally, we render <Controls paginationInfo={page} /> to display the pagination controls below the content.

This structure keeps your code clean and modular, allowing you to reuse the pagination system across multiple sections of your project with minimal effort.

createCollectionPagination — Quick Guide

createCollectionPagination generates a getStaticPaths function that automatically paginates an Astro collection. You provide:

Example
---
export const getStaticPaths = createCollectionPagination({
  collection: "components",
  pageSize: 3,
});

---