AdminKitDocs
Navigation

React Bootstrap Pagination

React Bootstrap pagination driven by state: a current page, Previous and Next buttons that disable at the ends, and a table that shows one page of rows.

Pro

This component is only available in AdminKit PRO. Learn more.

When a list is too long for one screen, pagination splits it into pages and lets people move between them. They see where they are, jump to a page by its number, or step through with Previous and Next. Use it below a table of orders, a list of search results or a grid of cards.

In AdminKit for React, pagination is Bootstrap's markup written in JSX. There is no component to import. You keep the current page in React state and render one item per page, with the current one marked active. Pagination is part of AdminKit PRO: the stylesheet of the free version leaves out Bootstrap's pagination styles.

Example

This Pagination component renders the pager and nothing else. The parent holds the current page and passes it in, with a function to call when the user picks another page. Previous is disabled on the first page and Next on the last.

src/components/pagination.tsx
type PaginationProps = {
  page: number;
  pageCount: number;
  onPageChange: (page: number) => void;
  label: string;
};

export function Pagination({ page, pageCount, onPageChange, label }: PaginationProps) {
  const pages = Array.from({ length: pageCount }, (_, index) => index + 1);
  const isFirst = page === 1;
  const isLast = page === pageCount;

  return (
    <nav aria-label={label}>
      <ul className="pagination">
        <li className={`page-item${isFirst ? " disabled" : ""}`}>
          <button
            type="button"
            className="page-link"
            disabled={isFirst}
            onClick={() => onPageChange(page - 1)}
          >
            Previous
          </button>
        </li>
        {pages.map((number) => (
          <li
            key={number}
            className={`page-item${number === page ? " active" : ""}`}
            aria-current={number === page ? "page" : undefined}
          >
            <button type="button" className="page-link" onClick={() => onPageChange(number)}>
              {number}
            </button>
          </li>
        ))}
        <li className={`page-item${isLast ? " disabled" : ""}`}>
          <button
            type="button"
            className="page-link"
            disabled={isLast}
            onClick={() => onPageChange(page + 1)}
          >
            Next
          </button>
        </li>
      </ul>
    </nav>
  );
}

The items are buttons because they change state on this page. When each page has its own URL, use React Router Links with the page-link class instead.

Classes

ClassElementEffect
pagination<ul>Joins the items into one row of connected boxes
page-item<li>One item
page-link<button> or <a>The box people click
active.page-itemMarks the current page in the primary color; add aria-current="page" too
disabled.page-itemGreys out an item and ignores clicks; also set disabled on the button
pagination-sm, pagination-lg.paginationSmaller or larger items

AdminKit makes Bootstrap's pagination a little more compact. In src/scss/1-variables/_app.scss, it sets the padding of the items to 0.3rem 0.75rem (0.15rem 0.6rem small, 0.35rem 1rem large). The items take the background of cards, in light and dark mode. Their text is --bs-gray-600, and --bs-gray-800 on hover. src/scss/3-components/_pagination.scss keeps the number of the active page white.

Pagination with a table

The most common use: a table that shows one page of rows at a time. Keep the current page in state, cut that page out of the rows with slice, and put the pager below the table. Here the rows come in as a prop:

src/components/orders-table.tsx
import { useState } from "react";
import { Pagination } from "@/components/pagination";

type Order = { id: string; customer: string; total: string };

const pageSize = 10;

export function OrdersTable({ orders }: { orders: Order[] }) {
  const [page, setPage] = useState(1);
  const pageCount = Math.max(1, Math.ceil(orders.length / pageSize));
  const rows = orders.slice((page - 1) * pageSize, page * pageSize);

  return (
    <div className="card">
      <div className="card-body">
        <table className="table table-striped">
          <thead>
            <tr>
              <th>Order</th>
              <th>Customer</th>
              <th className="text-end">Total</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((order) => (
              <tr key={order.id}>
                <td>{order.id}</td>
                <td>{order.customer}</td>
                <td className="text-end">{order.total}</td>
              </tr>
            ))}
          </tbody>
        </table>
        <Pagination
          page={page}
          pageCount={pageCount}
          onPageChange={setPage}
          label="Orders pages"
        />
      </div>
    </div>
  );
}

Tables has more on the table markup. If people also need to search and sort the rows, use the Data table plugin instead. It brings its own pager and draws it with the same pagination, page-item and page-link classes, so it looks the same as yours.

Sizes and alignment

On a busy card, a smaller pager takes less room. Add pagination-sm or pagination-lg to the list. To move the pager, use flexbox utilities such as justify-content-center or justify-content-end:

<nav aria-label="Invoices pages">
  <ul className="pagination pagination-sm justify-content-end">
    <li className="page-item active" aria-current="page">
      <button type="button" className="page-link">1</button>
    </li>
    <li className="page-item">
      <button type="button" className="page-link">2</button>
    </li>
    <li className="page-item">
      <button type="button" className="page-link">3</button>
    </li>
  </ul>
</nav>

Accessibility

  • Wrap the list in a <nav> with an aria-label that says what it pages through, such as "Orders pages". A page often has more than one navigation region.
  • Give the current page aria-current="page". The active class only changes its color.
  • Set disabled on the button of a disabled item. Then Tab skips it and a click does nothing. With an <a>, the disabled class alone does not stop the keyboard: add tabIndex={-1} and aria-disabled="true".
  • When Previous and Next show only an icon, give the button an aria-label, as the demo does.

Last updated on

On this page