20 CSS Pagination

20 hand-coded CSS pagination designs for admin dashboards, e-commerce product grids, blog archives, search results, and documentation. Covers pure-CSS :target pagination with no JavaScript, ellipsis truncation for large page ranges, responsive layouts that collapse to a compact page-X-of-N on mobile, jump-to-page and items-per-page controls, Tailwind and Bootstrap 5 variants, and a WCAG reference build with aria-current. 16 are pure CSS. Scoped under .pgn-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

16 pure CSS4 light JSPublished

Related39 CSS Breadcrumbs32 CSS Tab Designs15 CSS Navigation Menu Designs51 CSS Buttons

Pure CSS Pagination with :target — preview
01 / 20Pure CSS

Pure CSS Pagination with :target

The pagination every static-site and docs build actually wants: page state lives in the URL fragment, so a reload, a shared link or a back-button press all land on the right page with zero JavaScript. Four panels, four anchors, and a :target rule doing the switching — plus a :has() fallback that keeps page one visible when nothing is targeted at all.

Published

Pagination with Ellipsis — preview
02 / 20Light JS

Pagination with Ellipsis

Twenty-four page buttons in a row is unusable, so this one renders a sliding window — first, a three-page neighbourhood around the current page, last, and an ellipsis wherever the sequence actually skips. Under forty lines of dependency-free JavaScript, with disabled edges, a live-announced status line and ellipsis nodes that stay out of the tab order.

Published

Responsive Compact Pagination — preview
03 / 20Pure CSS

Responsive Compact Pagination

One markup, two layouts, no viewport media query: a full number row on wide containers that collapses to Previous · Page 3 of 12 · Next when its own wrapper gets narrow. Because it queries the container and not the screen, the same component behaves correctly in a sidebar, a modal and a full-width page — drag the demo's resize handle to watch it switch.

Published

Pagination with Prev Next Icons — preview
04 / 20Pure CSS

Pagination with Prev Next Icons

The four-control bracket every data view eventually needs — first, previous, next, last — drawn as inline SVG chevrons around a number row. Boundary state uses the native disabled attribute rather than a class, hit targets are a full 44 px, and every icon-only control carries a real accessible name.

Published

Pagination with Jump to Page Input — preview
05 / 20Light JS

Pagination with Jump to Page Input

Once a list runs to sixty-four pages, numbered buttons stop helping — you need a box to type into. This bar pairs Previous/Next with a bounded number input and a Go button, validates the entry in JavaScript (because min/max outside a form validate nothing), and reports both the error and the new record range to assistive tech.

Published

Pagination with Items Per Page — preview
06 / 20Light JS

Pagination with Items Per Page

The three-part bar every admin list converges on: page size on the left, a record range in the middle, a compact stepper on the right. Changing the page size resets to page one and recomputes the window — the exact contract an offset-based API expects — and the native <select> is restyled without losing its real menu or keyboard type-ahead.

Published

Tailwind CSS Pagination Component — preview
07 / 20Pure CSS

Tailwind CSS Pagination Component

The utility-first pagination bar written out in full, with the exact class string you would paste into a Tailwind project — plus the @apply-style component classes that stop twelve number buttons from each carrying forty utilities. No CDN and no build step: the demo reproduces those utilities as scoped vanilla CSS so it renders identically in an isolated frame.

Published

Bootstrap 5 Custom Pagination — preview
08 / 20Pure CSS

Bootstrap 5 Custom Pagination

Bootstrap 5.3 exposes pagination through --bs-pagination-* custom properties, so restyling it no longer means a specificity war or a Sass rebuild. This keeps the canonical .pagination / .page-item / .page-link structure and its .active and .disabled states, and rewrites only the variables — neutral surface, softer radius, a near-black active fill instead of the default blue.

Published

Accessible ARIA Pagination — preview
09 / 20Pure CSS

Accessible ARIA Pagination

The reference implementation to copy when accessibility is the requirement, not an afterthought: a labelled <nav> landmark, an ordered list, aria-labels that read as sentences ("Page 5, current page"), a 2 px :focus-visible ring with offset, a redundant non-color cue on the active page, and a forced-colors block so the state survives Windows high contrast mode.

Published

Data Table Pagination Bar — preview
10 / 20Pure CSS

Data Table Pagination Bar

The footer bar every admin table grows: record range on the left, page control on the right, both hanging off the same card. Paging is pure CSS — hidden radios plus a :has() rule hide every row whose data-page does not match — so arrow-key traversal comes free and the state survives a repaint.

Published

E-Commerce Grid Pagination — preview
11 / 20Pure CSS

E-Commerce Grid Pagination

A category-page footer that does the two things shoppers actually need: a stable card grid that never reflows as images land, and a "Showing 1–4 of 96 products" line that says where you are in the catalogue. Auto-fit columns, locked 4:3 imagery, a windowed number row with an ellipsis, and no media queries anywhere.

Published

Blog Archive Pagination — preview
12 / 20Pure CSS

Blog Archive Pagination

Archive pagination is read, not clicked, so the directional links carry the destination article's title under a Newer / Older label — the reader knows what is on the other side before committing. A three-column grid pins the page numbers dead centre no matter how long those titles run, and the post links use an animated background-size underline that never shifts the baseline.

Published

Cursor-Based Load More Pagination — preview
13 / 20Light JS

Cursor-Based Load More Pagination

Offset pagination breaks on live data: insert a row while someone is reading and page two silently repeats or skips records. Cursor paging fixes it by pointing at the last row you saw, and the UI has to change too — no page count, no jump-to-40, just Load newer above the feed and Load older below, with each row exposing its own cursor id.

Published

Sliding Pill Active Pagination — preview
14 / 20Pure CSS

Sliding Pill Active Pagination

One pill that slides between five positions instead of five backgrounds blinking on and off. Built on a native radio group, so arrow-key traversal is free, and driven entirely by a custom property the parent selector rewrites — the movement is a single compositor-only translate with a little overshoot.

Published

Hover Underline Pagination — preview
15 / 20Pure CSS

Hover Underline Pagination

A dark, typographic pagination row where the interaction is the design: a rule wipes in from the left, the digit lifts and grows a fraction, and both effects run on :hover and :focus-visible so a keyboard user gets identical feedback. Nothing animated touches layout.

Published

3D Flip Perspective Pagination — preview
16 / 20Pure CSS

3D Flip Perspective Pagination

Each page key is a two-faced box: the number on the front, a verb on the back. Hover or focus rotates it a half turn on X and the browser swaps which face points at the camera. Perspective lives on each button rather than the row, so the outer keys do not skew, and reduced motion falls back to a plain background change.

Published

Vertical Side-Docked Pagination — preview
17 / 20Pure CSS

Vertical Side-Docked Pagination

For sectioned long-form content the index belongs on the same axis as the reading: a sticky rail of labelled dots docked to the right edge, with a fill bar driven by a scroll-progress timeline where supported. Scroll snapping makes each jump land cleanly, and every dot is a real anchor so deep links survive a reload.

Published

Full-Width Fluid Pagination — preview
18 / 20Pure CSS

Full-Width Fluid Pagination

The docs-footer layout: previous and next articles pinned to the outer edges with their titles, page numbers exactly in the middle. Flexbox cannot actually do this — the centre drifts as soon as the two labels differ in length — so it is a three-track grid, which stays centred at any width and folds to a two-column stack on narrow screens.

Published

Glassmorphism Pagination — preview
19 / 20Pure CSS

Glassmorphism Pagination

Frosted glass only reads as glass over something worth blurring, so the bar floats over a photograph with a contrast scrim beneath it. Low-alpha fill, an 18 px backdrop blur with a saturation boost, a lit hairline edge and an inset top highlight — plus a solid fallback for engines without backdrop-filter.

Published

Neumorphism Soft UI Pagination — preview
20 / 20Pure CSS

Neumorphism Soft UI Pagination

Soft UI done with the contrast problem actually solved: extruded keys from a dual shadow pair, an inset press state for the active page, and a real accent colour plus weight change on the current item — because a shadow-only affordance sits near 1.5:1 and fails WCAG on its own. Includes a forced-colors block that restores visible borders.

Published

FAQ

Frequently asked questions

Can you build pagination in pure CSS with no JavaScript?
Yes, for the case where all pages are already in the DOM. The :target pseudo-class matches an element whose id equals the current URL fragment, so linking to #page-3 makes that panel match :target and you show it while hiding the rest. The whole mechanic is: hide every panel, then #page-1:target, #page-2:target { display: block }, plus a :not(:target) default so page one shows on first load when there is no fragment yet. The active state on the number itself comes from .panel:target ~ nav a[href="#page-3"] or, more cleanly, a :has() selector on the container. Two honest limits. This is client-side only — the URL fragment is never sent to the server, so it cannot paginate content that has not been loaded, which rules it out for a 10,000-row table. And it changes the browser history, so Back steps through pages, which is usually what people want but occasionally is not. Where it shines is documentation tabs, FAQ sections, image galleries and any paginated block whose total content is small enough to ship at once. Demo 01 is the reference.
How do I handle pagination with hundreds of pages?
You truncate with an ellipsis, and the rule most implementations get wrong is that the window must be stable. The standard shape is: first page, a leading ellipsis when needed, a window of pages around the current one, a trailing ellipsis, then the last page — for example 1 … 7 8 [9] 10 11 … 214. Keep the number of rendered items constant as the user moves; if the window shrinks near the edges the whole bar changes width and the numbers shift under the cursor, which causes mis-clicks. The fix is to expand the window when you are near either end so the total count stays the same: at page 2 you render 1 [2] 3 4 5 6 … 214 rather than a short bar. On the markup, an ellipsis is not a page, so it must not be a link or a button — use a <span aria-hidden="true"> so keyboard users tab from page to page without landing on a dead stop, and screen readers do not announce a meaningless character. Demo 02 implements the stable-window version.
What is the accessible way to mark up pagination?
Four things, and they are all small. Wrap the control in <nav aria-label="Pagination"> — the label matters because most pages have several navigation landmarks and "navigation" alone tells a screen-reader user nothing. Put the pages in a list so the count is announced ("list, 9 items"), which gives blind users the same at-a-glance sense of scale that sighted users get. Mark the active page with aria-current="page" — that is the semantically correct value here, not aria-current="true", and style off the attribute rather than a separate class so the visual and the semantic state can never drift apart. Finally, make the numbers real <a href> elements when they navigate, or real <button> elements when they update in place; a <div onclick> is invisible to keyboards. Two extras worth doing: give each link an accessible name beyond the bare digit (aria-label="Page 3") so it is unambiguous in a links list, and include a visually-hidden live region announcing "Showing 21-40 of 214" after a page change. Demo 09 is the reference implementation.
How should pagination change on mobile?
A full number row does not fit a 360px screen, and shrinking the tap targets to make it fit fails WCAG 2.5.5, which asks for at least 24 by 24 CSS pixels and in practice wants 44. The better pattern is to change the control rather than compress it: show prev and next as large targets with a compact "Page 9 of 214" readout between them, and drop the individual numbers entirely. Users on phones rarely jump to page 47 — they step forward, or they filter. Implement the switch with a container query rather than a media query: @container (max-width: 30rem) responds to the space the pagination actually has, so the same component works in a narrow sidebar on desktop and in a full-width mobile layout without special cases. Hide the numbers with display: none rather than visibility: hidden so they leave the tab order too. If you want a middle tier, an intermediate breakpoint can keep a three-number window with the ellipsis removed. Demo 03 shows the full-to-compact transition.
When should I use load-more or infinite scroll instead of numbered pages?
It depends on whether the user needs a stable address for a position in the list. Numbered pagination wins when results are ranked or shareable — search results, product listings, documentation — because a URL like ?page=7 can be linked, bookmarked and crawled, and users can tell how much is left. Cursor-based load-more wins for chronological feeds where new items arrive constantly: offset pagination with a moving dataset shows duplicates and skips items, because inserting a row at the top shifts everything down and page two now starts one row later than it did a second ago. A cursor keyed to the last item's id or timestamp is immune to that. Avoid true infinite scroll on anything with a footer, on anything users need to leave and return to, and on anything you want indexed — Google renders pages but does not scroll them, so content only reachable by scrolling may never be crawled. The practical middle ground, and the pattern most e-commerce sites converge on, is a "Load more" button that also updates the URL, giving you both the shallow interaction and the addressable state. Demo 13 covers the cursor pattern.
Does pagination affect SEO, and what should I do about it?
It matters, and the guidance changed in a way many older tutorials have not caught up with. Google deprecated rel="next" and rel="prev" as an indexing signal in 2019 — they are harmless to keep for other consumers but they do nothing for Google now. What matters instead: every paginated page must be reachable by a real crawlable <a href> link, so pagination driven purely by JavaScript click handlers with no href can leave pages two onward undiscovered. Each page should self-canonicalize to its own URL, not to page one — canonicalizing every page to the first is a common and costly mistake that tells Google the other pages do not deserve indexing. Do not create a "view all" page and canonicalize to it unless that page genuinely loads fast. Keep the URL pattern boring and stable (/blog/page/3/ or ?page=3), and make sure page one is available at the bare URL rather than at ?page=1, so you do not split signals across two addresses for the same content. Finally, paginated pages need genuinely distinct titles or Search Console will flag duplicates.
How do I style the active and disabled states correctly?
Style from state, not from a hand-applied class, so the visual and the semantic can never disagree. For the current page, target [aria-current="page"] directly — then setting the attribute is the single source of truth and there is no way to end up with a highlighted link that screen readers do not announce as current. For prev and next at the ends of the range, the control should be genuinely inert rather than just faded: if it is a <button>, use the disabled attribute; if it is an <a>, remove the href and add aria-disabled="true", because a link without href is not focusable and cannot be activated, whereas a greyed-out link with a live href is a trap. Never rely on colour alone for either state — the active page should differ in weight, background or a visible indicator, not just hue, since a red-versus-grey distinction is invisible to a significant share of users. Contrast applies to the disabled state too: WCAG exempts disabled controls from the 4.5:1 requirement, but a control nobody can read is still a bad control, so aim for 3:1 anyway.
How do these pagination designs port to React, Vue, Svelte and Tailwind?
The 16 pure-CSS designs port unchanged — copy the markup and the scoped .pgn-NN rules, and they behave identically in any framework. In component terms the natural interface is currentPage, totalPages and an onPageChange callback, with the ellipsis window computed in a small pure function you can unit-test separately from the rendering. That function is the only genuinely tricky part of pagination and it is worth extracting. For the four demos with JavaScript, wire them into the usual hooks: useEffect with a returned cleanup in React, onMounted plus onBeforeUnmount in Vue, onMount returning cleanup in Svelte. In the Next.js App Router, prefer driving pagination through searchParams so the server component re-renders with the right slice and the URL stays shareable — that keeps pages crawlable, which a purely client-side implementation does not. Demo 07 is already written as Tailwind utilities and demo 08 as Bootstrap 5 classes, both scoped so they cannot leak into a host page. Each design is roughly 1-3KB of CSS.

Related collections

32 CSS Accordions — Vertical & Horizontal preview

32 CSS Accordions — Vertical & Horizontal

32 free CSS accordions covering pure-CSS FAQ blocks with details/summary, mobile navigation menus, e-commerce filter sidebars, nested tree views, exclusive single-open groups and smoothly animated height:auto — plus 26 visual variations in vertical, horizontal and hybrid layouts. Copy-paste HTML and CSS, no JavaScript.

39 CSS Breadcrumbs preview

39 CSS Breadcrumbs

39 hand-coded CSS breadcrumbs — Schema.org BreadcrumbList Microdata + WCAG 2.2 accessible (Google Rich Results eligible), :has() responsive collapse, dark/light theme toggle with color-mix, Shopify/Amazon e-commerce filter chip removal, Vercel/Linear/Notion SaaS dashboard app-shell pattern, Docusaurus/Mintlify sticky scroll-shrink header, Amazon-style dropdown sibling menus, NFT/Web3 holographic shimmer (@property + conic-gradient), SaaS marketing gradient text, Airbnb filter chip with container queries, editorial blog wave underline, developer docs one-click path copy (Clipboard API), and 26 more. Semantic HTML: <nav aria-label='Breadcrumb'> + <ol> + aria-current='page'. Framework-agnostic, MIT-licensed.

26 CSS Circular Menus preview

26 CSS Circular Menus

26 hand-coded CSS circular and radial menu designs, from the floating-action FAB speed dial and dashboard radial hubs to game-style pie-slice wheels, SVG gooey metaball fans, hexagonal honeycombs, iris-aperture reveals, orbiting satellites on offset-path, compass-rose directional nav, sci-fi HUD rings, glassmorphism popovers built on the native Popover API, vinyl-record players, and full-takeover nebula overlays on native dialog. Every design uses CSS trigonometry (sin(), cos()) or the counter-rotation trick for positioning, ships real keyboard navigation, aria-labels on icon-only buttons, and a prefers-reduced-motion guard. Copy-paste HTML, CSS and vanilla JS with zero framework dependencies — MIT licensed.

Search CodeFronts

Loading…