react-paginate
A lightweight, accessible ReactJS component for rendering customizable pagination controls.
Repository Health
Technical Analysis
react-paginate is a small, dependency-light React component (its only runtime dependency is prop-types) for rendering pagination controls in any list-based UI. It renders an unstyled <ul>/<li> widget driven purely by props, leaving all visual styling to the consumer’s own CSS, and supports both controlled (forcePage) and uncontrolled (initialPage) usage patterns that match React’s standard conventions for buildable, controllable components.
Under the hood, the component computes which page numbers and ellipsis (“break”) elements to display based on pageRangeDisplayed and marginPagesDisplayed, collapsing runs of hidden pages behind a single break indicator and re-expanding them when only one page would otherwise be hidden. It exposes accessibility hooks such as ARIA roles, aria-current, and configurable aria-label builders, plus an hrefBuilder callback so page links can point at real, crawlable URLs instead of relying solely on onClick handlers.
What You Get
- Fully accessible pagination markup — ARIA roles, aria-current, and configurable aria-label builders out of the box.
- Both controlled (forcePage) and uncontrolled (initialPage) usage modes for whatever state pattern your app uses.
- Automatic ellipsis (“break”) collapsing/expansion so large page counts don’t render hundreds of page links.
- An hrefBuilder callback for generating real, crawlable href values instead of relying solely on click handlers.
- TypeScript typings shipped via index.d.ts, no need for a separate @types package.
Common Use Cases
- Paginating a table or list of search results fetched page-by-page from an API.
- Building SEO-friendly paginated blog/article archives where each page needs a real crawlable URL.
- Adding client-side pagination to an in-memory array of items, such as a filtered product grid.
- Synchronizing two independent pagination widgets against the same data set, as shown in the project’s own CodePen demos.
Under The Hood
Architecture
The library is a small, single-purpose UI package organized around one class component, PaginationBoxView (react_components/PaginationBoxView.js), composing two small function components, PageView and BreakView, plus a tiny utils.js helper (classNameIfDefined). State is local to PaginationBoxView (this.state.selected), synced with an optional controlled forcePage prop via componentDidUpdate. Data flow is one-directional: click handlers (handlePreviousPage, handleNextPage, handlePageSelected, handleBreakClick) funnel into a single handleClick dispatcher that consults an optional onClick prop, letting a parent intercept or override the pending page change, before calling handlePageChange, which updates internal state and invokes onPageChange. The pagination() method computes visible page/break items in two passes — building a candidate page/break list honoring marginPagesDisplayed/pageRangeDisplayed, then collapsing any break that covers only one hidden page — a small but nontrivial pure-computation core that represents most of the library’s real complexity.
Tech Stack JavaScript (ES2015+ class and arrow-function properties) built on React as a wide peer-dependency range, with prop-types as the only runtime dependency. Build tooling is Webpack with Babel presets compiling a UMD bundle for distribution, and the local demo adds a fast-refresh plugin. Testing runs on Jest with a jsdom environment and React Testing Library. Linting uses ESLint with the React plugin, formatting is handled by Prettier, and a Husky pre-commit hook runs the linter before each commit. The bundled demo app runs on Express with static/dev-middleware serving. TypeScript typings are hand-maintained separately in index.d.ts rather than generated from source.
Code Quality Two test files cover default rendering, pageRangeDisplayed/marginPagesDisplayed edge cases, zero-page-count rendering, and PageView’s active/aria-current output, but there is no dedicated test file for BreakView or for the onClick interception contract (returning false or a page number), so that behavior is exercised only indirectly. Error handling favors console warnings over exceptions for invalid initialPage/forcePage/pageCount combinations. Naming is consistent and descriptive, PropTypes provide runtime validation in place of static types in the source itself, and there is no active CI workflow beyond a stale legacy config.
API Design The public API is a single default export configured entirely through props, with sensible defaults for every optional prop so a working pagination widget renders with only the required pageCount prop. Escape hatches are well considered rather than bolted on: hrefBuilder for real anchor hrefs, ariaLabelBuilder and breakAriaLabels for accessibility customization, pageLabelBuilder for custom label text, and an onClick contract that lets advanced consumers cancel or redirect the built-in page change. Naming stays consistent across the many documented class-name and label props, the README documents each one in a single table, and TypeScript consumers get typings out of the box. The main friction point is that the library intentionally ships no CSS, so first-time users must write their own stylesheet before the component is visually usable.
Used by 3 apps in this directory
AFFiNE
Productivity · Project Management · Note Taking
Write, draw, and plan in one infinite canvas — the open-source alternative to Notion and Miro that keeps your data yours.
GrowthBook
Developer Tools · Analytics · Monitoring
Open source feature flags, A/B testing, and warehouse-native experimentation that queries your existing data infrastructure—no data movement required.
Logto
Authentication
Open-source auth infrastructure for SaaS and AI apps with OIDC, SAML, and RBAC