Does Tailwind CSS include a pagination component?
No. Core Tailwind gives you utility classes and nothing prebuilt, so the pager is either assembled by hand or taken from a kit.
Because Tailwind is a utility-first framework, the control comes down to a nav element, a list of links and a handful of classes on each link. No extra stylesheet is involved.
Tailwind Labs did sell finished versions as part of Tailwind Plus, its paid collection of UI blocks. Its availability has changed, which is covered further down.
Where the finished pagination pattern comes from depends on the project. Some people write the markup and classes directly in the template. Others pull from a kit, either paid blocks such as Tailwind Plus (sold until September 2026, when it closed to new customers) or free snippet libraries such as HyperUI. Laravel already ships Tailwind-compatible paginator views, and php artisan vendor:publish --tag=laravel-pagination copies tailwind.blade.php into the project for editing (Laravel documentation).
Tailwind v4 moves theme configuration into CSS through @theme, but that changes nothing here. Pagination is still plain utilities and variants.
How to build Tailwind pagination step by step
Build it as a nav element holding a list of links, one list item per control, with identical base classes on every link and a real URL in every href.
Markup and classes
- Wrap the control in
<nav aria-label="Pagination"> so assistive technology exposes it as a landmark. - Add a
<ul class="flex items-center gap-1"> with one <li> per control. - Every link gets the same base classes:
h-10, border, rounded-sm, text-sm and a hover background. - Number links are fixed at
w-10 with justify-center so one and two digit numbers line up. Previous and Next size themselves with px-3. - Put a real URL in every href, such as
?page=2. - Mark the current page with
aria-current and style it through a variant (covered below).
<nav aria-label="Pagination">
<ul class="flex items-center gap-1">
<li><a href="?page=1" class="flex h-10 items-center rounded-sm border border-gray-300 px-3 text-sm text-gray-700 hover:bg-gray-100">Previous</a></li>
<li><a href="?page=1" class="flex h-10 w-10 items-center justify-center rounded-sm border border-gray-300 text-sm text-gray-700 hover:bg-gray-100">1</a></li>
<li><a href="?page=2" class="flex h-10 w-10 items-center justify-center rounded-sm border border-gray-300 text-sm text-gray-700 hover:bg-gray-100">2</a></li>
<li><a href="?page=3" class="flex h-10 items-center rounded-sm border border-gray-300 px-3 text-sm text-gray-700 hover:bg-gray-100">Next</a></li>
</ul>
</nav>
Heroicons chevrons work well for the Previous and Next arrows. Paste them in as inline SVG in the HTML so they pick up the link color through currentColor.
If you copy v3 snippets into a v4 project, the utility scale has shifted. rounded becomes rounded-sm, shadow becomes shadow-sm, and the default ring width drops from 3px to 1px (Tailwind v4 upgrade guide).
Shape variants
| Variant | Key classes | Fits |
|---|
| Rounded square | h-10 w-10 rounded-sm | Standard numbered lists |
| Circular | h-10 w-10 rounded-full | Soft, compact UI |
| Simple | Previous, Next, “Page 2 of 10” text | Linear content, small screens |
| Button group | -ml-px, first:rounded-l-sm, last:rounded-r-sm | Admin tables |
Whichever shape you choose, use it everywhere. The markup, the states and the href rules don’t change between the four.
How to style the current page and disabled buttons
Set aria-current="page" on the active link and style it with an aria-[current=page]: variant, so the visual state and the screen reader state read from one attribute.
<a href="?page=2" aria-current="page"
class="... aria-[current=page]:border-indigo-600 aria-[current=page]:bg-indigo-600 aria-[current=page]:text-white">2</a>
GitHub’s Primer treats the current-page style as an accessibility requirement. It has to meet minimum color contrast, not only look different from its neighbors.
The disabled ends need a little more care. On the first page, Previous renders as an anchor with no href and aria-disabled="true". Without an href there is nothing to activate from the keyboard, and the built-in aria-disabled: variant handles the look.
<a aria-disabled="true" class="flex h-10 items-center rounded-sm border border-gray-200 px-3 text-sm text-gray-400 aria-disabled:cursor-not-allowed aria-disabled:opacity-50">Previous</a>
Sources disagree on how to disable the ends. The native disabled attribute works on button elements and drops the control from the tab order, but it is invalid on anchors. eBay’s MIND pattern keeps the bookend links focusable and adds aria-disabled, so a greyed-out control is also announced as disabled. HeroUI’s Pagination documents both :disabled and [aria-disabled="true"] as styling hooks.
For link-based pagination, aria-disabled wins. Buttons that run JavaScript can keep the native attribute.
In v4, hover: styles apply only on devices that support hover, so touch screens no longer keep a stuck hover color (v4 upgrade guide). Add focus-visible:outline-2 focus-visible:outline-offset-2 for keyboard users.
Responsive Tailwind pagination with an ellipsis for long page lists
On narrow screens, hide the numbered links and leave Previous and Next in place. For long page counts, collapse the range so only the first page, the last page, the current page and its siblings show, with an ellipsis on either side.
Hiding page numbers on small screens
Tailwind’s sm breakpoint starts at 40rem, which is 640px (Tailwind CSS documentation).
Breakpoints follow mobile-first design. Unprefixed utilities apply at every width, and prefixed ones apply from their breakpoint up. So hidden sm:block hides a number link on phones and shows it from 640px.
The docs warn against reading sm: as “on small screens”. It means “at the small breakpoint and above”.
Each prefix compiles to a media query, @media (width >= 40rem) in v4, so no hand-written CSS is needed.
<li class="hidden sm:block">...number link...</li>
<li class="sm:hidden">Page 2 of 10</li>
Primer advises against hiding page links on narrow viewports. Use this pattern only where Previous and Next cover the navigation, as with linear content, and keep the “Page 2 of 10” text so the position stays visible.
Showing result counts
A summary line such as “Showing 21 to 30 of 100 results” sits beside the control on wide screens. HeroUI ships it as a dedicated summary part of its Pagination component.
The first item is (current page – 1) x per page + 1, and the last is the smaller of current page x per page and total results. Total pages is total results divided by per page, rounded up.
Take page 3 with 10 per page and 100 results, and you get 21 to 30 of 100.
Calculating siblings and ellipsis
The slot count should stay fixed at 2 x siblings + 5. That covers the first page, the last page, the current page, the siblings on both sides and two ellipsis slots, so the row never changes width as the user moves through the pages.
| Current page | Siblings | Row for 20 pages |
|---|
| 1 | 1 | 1 2 3 4 5 … 20 |
| 6 | 1 | 1 … 5 6 7 … 20 |
| 20 | 1 | 1 … 16 17 18 19 20 |
The ellipsis is a plain span, not a link, and takes aria-hidden="true" so screen readers skip it (HeroUI Pagination documentation).
react-paginate computes the range and renders the break element for you. TanStack Table tracks page index and page size but leaves the page-number row to you.
What makes Tailwind pagination accessible?
Utility classes style the control, but the markup has to carry the semantics. That means a named nav landmark, aria-current on the active link, a visible focus style, disabled ends that get announced, and targets big enough to hit.
GitHub’s Primer design system has a clear pattern here. The nav landmark is named “Pagination” through aria-label="Pagination" (Primer), each link carries an ARIA label such as “Page 1” or “Previous Page”, and the active one gets aria-current="page".
Keyboard order follows the DOM, so Tab walks the links left to right and ends on Next (eBay’s MIND pagination pattern). With client-side pagination, the new page also has to be announced after a link is activated.
Pair focus-visible:outline-2 with focus-visible:outline-offset-2 on every link, and never strip the outline without replacing it.
Target size is the easy part. WCAG 2.2 sets the minimum at 24 by 24 CSS pixels (W3C, WCAG 2.2 SC 2.5.8, Level AA), and the usual h-10 w-10 number link is 2.5rem, which is 40px at the default 16px root size (Tailwind CSS spacing documentation).
Reflow is harder. Primer requires the control to stay readable at a 320px viewport with no horizontal scrolling, and it warns against hiding page links on narrow viewports, since anything available on wide screens has to stay available on narrow ones. A long number row needs a collapsed range with ellipses before it needs hidden links.
Do search engines follow Tailwind pagination links?
Google generally crawls URLs found in the href of <a> elements, and it treats every distinct URL as a separate page. A pager built from buttons hands the crawler nothing to follow.
Googlebot does not click, so the next-page control cannot depend on a handler (Google Search Central, last updated December 10, 2025). Pagination wired up through JavaScript click events alone never exposes page two.
Kit snippets vary in how close they come to that rule.
| Source | Page link element | Crawlable as shown |
|---|
| Material Tailwind (HTML) | button | No |
| daisyUI | button with join-item and btn | No |
| Flowbite | a with href=”#” | Only after real URLs replace the placeholder |
| shadcn/ui | a, with aria-current on the active link | Yes, once each href is real |
daisyUI’s classes are not the obstacle, since its documentation says join-item can sit on a button, an input or other elements. Only the examples default to buttons.
A copied Flowbite link with href="#" fails a second rule. Google ignores everything after a hash, so every page resolves to the same URL.
A few other details are worth getting right. Each page’s canonical URL should name itself, never page one. Google no longer reads rel prev and next tags, although other search engines may still use them. Sort and filter variants such as ?order=price need noindex or a robots.txt rule. And an optional link from every page back to page one hints that it is the better landing page.
Titles and descriptions can repeat across the sequence. Google allows it explicitly.
Tailwind pagination in Laravel, React, Vue and Next.js
The pager stays the same nav, list and anchors in every stack. What changes is who supplies the page number and the URL.
Laravel and Livewire
Laravel’s paginator already carries what the markup needs.
On the backend, firstItem(), lastItem() and total() feed the summary line, and withQueryString() keeps active filters in every link.
Tailwind v4 adds one trap. Class detection skips files that .gitignore lists, and vendor/ is one of them, so the built-in views need an @source line in resources/css/app.css.
@import 'tailwindcss';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
Fresh Laravel projects already carry that line, according to the Laravel docs. After a manual upgrade to v4, check it first when the pager renders unstyled.
Livewire ships its own Tailwind views, tailwind.blade.php and tailwind-simple.blade.php, and tracks the current page in ?page= by default (Livewire docs). php artisan livewire:publish --pagination copies them into the project for editing.
Inertia.js
Inertia hands the paginator’s JSON to the page, so the pager becomes one component looping over a links array.
Each entry has a url, which is null for Previous on the first page, Next on the last page and the “…” gaps, a label with the visible text (Previous and Next included), and an active flag that is true for the current page. Map a null url to the disabled end or a plain ellipsis, and active to the current page.
React and Next.js
Keep the page in the URL, not in useState. App Router layouts do not receive searchParams, because a shared layout is not re-rendered on navigation and could show a stale page number (Next.js docs).
Pages read the value from the searchParams prop, which is a promise from Next.js 15 onward. Client components call useSearchParams() instead, which is read-only.
With Tailwind CSS already set up in React, each page link becomes a next/link with ` href={?page=${n}} . shadcn/ui's PaginationLink renders a plain anchor by default, and its docs show the swap to Link` for client-side navigation.
Vue
Build the Vue pager as a controlled component. The parent owns the current page, passes it down as a prop and receives an update event.
Drive that page from the route query. RouterLink renders an anchor with an href by default, so the links stay crawlable without extra work (Nuxt SEO guide).
Static generation is the catch. Every intended page URL has to be part of the build.
Which Tailwind pagination library should you use?
For one paginated list, hand-built markup beats a library. A kit pays off when pagination is one of many components that have to look alike.
Libraries earn their place once the pager sits beside tables, tabs and modals, which is where a Tailwind UI kit removes the repeated styling work.
The deciding factor is consistency across a design system, not the pager itself.
Tailwind Plus, which was the official paid source of ready-made blocks, no longer takes new customers. Tailwind Labs announced on September 9, 2026 that it is joining Shopify and closing sign-ups, while existing customers keep access (Tailwind Labs announcement).
| Option | How it ships | Notable detail |
|---|
| Tailwind Plus | Was sold as paid blocks in HTML, React and Vue | Closed to new customers since September 2026 |
| Flowbite | Open-source library, HTML markup plus ports such as Svelte | Icon and previous/next-only variants |
| daisyUI | Free plugin with semantic classes (join, join-item, btn) | Sizes from btn-xs to btn-xl, radio-input variant |
| Preline UI | Open-source prebuilt components | Page jumper and dropdown variants for long ranges |
| HyperUI | MIT-licensed copy-paste HTML, no installation | Pagination snippets written for Tailwind v4 |
| shadcn/ui | CLI copies React source into the project | isActive sets aria-current=”page” |
Cell details come from each project’s own documentation and repository.
The right pick depends on the setup. For one list in one app, hand-build it and skip the dependency. Several apps sharing a look point to shadcn/ui on React, otherwise Flowbite or Preline UI. HyperUI suits anyone who wants copy-paste HTML with nothing to install, and daisyUI gives shorter class lists at the price of its semantic class layer.
When Tailwind pagination is the wrong pattern
Numbered pagination fits result sets people revisit and compare. It fits poorly in feeds and in tables with expensive total counts, or anywhere a pager can’t give each page its own URL.
Google’s pattern comparison lays out the trade-offs (Google Search Central). Pagination shows result size and position, at the cost of more controls and a page load per step. “Load more” keeps everything on one page but can’t carry a very large result set. Infinite scroll feels natural, though unclear result size causes scroll fatigue, and it has the same size limit.
Large tables add a database cost. Laravel’s paginate() counts every matching record before it fetches a page.
simplePaginate() skips the count, and cursorPaginate() goes further with where clauses on indexed columns, but neither renders page numbers. A Tailwind row of numbers is only possible with paginate().
Dynamic class names break production builds. Tailwind scans source files as plain text, so a concatenated string never produces a real class (Tailwind CSS documentation).
// Breaks: "bg-indigo-600" never exists as a whole string
className={`bg-${active ? "indigo" : "white"}-600`}
// Works: both class strings exist in full
className={active ? "bg-indigo-600 text-white" : "bg-white text-gray-700"}
State kept outside the URL fails quietly. Livewire tracks the current page in ?page= by default, and the WithoutUrlPagination trait switches that off, so a reload returns to the first page.
Filters need the opposite care. Call resetPage() when a search term changes, or a user on page 4 lands on a result set that only has two pages (Livewire docs).
A pager that fetches pages through Ajax and never touches the address bar has the same weakness. No back button, no shareable link.
Tailwind pagination FAQ
How does Tailwind pagination differ from Bootstrap pagination?
Bootstrap ships a finished pagination component, complete with its own page-link and active classes. With Tailwind there is no such thing, just utilities, so the markup carries every style.
Laravel supports both. Tailwind views are the default, and a paginator call in the AppServiceProvider switches it to Bootstrap.
How do I add dark mode to Tailwind pagination?
Pair each light class with a dark: variant, such as dark:border-gray-700 and dark:hover:bg-gray-800, on every link, the current page included.
Tailwind’s dark variant follows the operating system setting by default. A manual toggle needs a custom variant declared in CSS.
Is Tailwind Plus pagination free?
No. Tailwind Plus was a paid product, sold as a one-time purchase with lifetime access, and Tailwind Labs closed it to new customers in September 2026 (Tailwind Labs announcement).
HyperUI snippets are MIT-licensed and free, and Flowbite’s core library is open source.
How many items should each page show?
No single number is correct. Laravel’s documentation examples use 15 items per page, and data tables often add a rows-per-page selector so users choose.
Whatever the value, the result summary and the total page count both derive from it.
What to Test Before Tailwind Pagination Goes Live
Test against the production build, not the dev server, since a class that Tailwind never generated makes every later visual check meaningless. Order matters here.
Start with the compiled CSS and confirm the active-page and disabled-end styles exist. Then check the page source to see that every number sits in an anchor with its own URL. Last comes a keyboard pass at a narrow width, on the first, last and out-of-range pages.
The keyboard pass goes last because it depends on final markup. Edge pages go with it, since the disabled ends and the ellipsis only render at those positions.
This order holds for Tailwind v4 as verified on October 29, 2026. Another release that renames utilities would put a compiled-CSS diff ahead of everything else.
Pagination is one control on a page, and a full web accessibility checklist covers the rest.