Pagination

The most challenging aspect was deciding whether or not the double ellipsis was necessaary. I concluded on making this conditional if the items needed exceeded a certain number. I think this approach makes the component versatile for anyone to use which is why I stuck with this approach.

Props & Styling

Props

Pagination

PropTypeDefaultDescription
activePagenumber— (required)Current page (1-indexed).
totalPagesnumber— (required)Total number of pages.
maxButtonsnumber5Maximum number of page buttons shown before collapsing into ellipses.
onPageChange(page: number) => void— (required)Called with the target page whenever a page button, or Previous/Next, is activated.
childrenReactNodeComposed of PaginationContent, PaginationPrevious, PaginationNext.

PaginationContent / PaginationPrevious / PaginationNext — no props. Each reads activePage, totalPages, and onPageChange from context, so they must be rendered inside <Pagination>.

Styling

The page-number logic lives in generatePaginationRange() (exported from PaginationContext.tsx), decoupled from rendering — it decides between showing no ellipsis, one ellipsis (near either end), or two ellipses (in the middle of a long range) based purely on how close activePage is to 1 and totalPages. PaginationPrevious/PaginationNext disable themselves automatically at the first/last page, switching to a muted gray and cursor-not-allowed rather than hiding — keeping the control's position stable as the user pages through.

Code

import React from 'react'
import { PaginationProvider } from './PaginationContext'

interface PaginationProps {
    activePage: number
    totalPages: number
    maxButtons?: number
    onPageChange: (page:number) => void
    children: React.ReactNode
}

const Pagination = ({activePage, totalPages, maxButtons = 5, onPageChange, children}: PaginationProps) => {
  return (
    <nav role="navigation" aria-label="pagination" className="flex flex-row items-center justify-center w-full">
      <PaginationProvider
        activePage={activePage}
        totalPages={totalPages}
        maxButtons={maxButtons}
        onPageChange={onPageChange}
      >
        {children}
      </PaginationProvider>
    </nav>
  )
}

export default Pagination