# Pagination Prev Next Compact — React Pagination Component

> A compact previous/next pager with a page-of-total readout whose number rolls up or down in the direction you are paging.

- Category: [Pagination](https://pixelpapa.com/components/pagination.md)
- Tags: pagination, compact, rolling number, pager, table footer
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/pagination/pagination-prev-next-compact

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/pagination-prev-next-compact.json"
```

The file is written to `components/pixelpapa/pagination-prev-next-compact.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { ChevronLeft, ChevronRight } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";

type CompactPagerProps = {
  page: number;
  total: number;
  onPageChange: (page: number) => void;
  className?: string;
};

export function CompactPager({ page, total, onPageChange, className }: CompactPagerProps) {
  const reduce = useReducedMotion();
  // Direction is derived from the previous page so it also works when the parent changes `page`.
  const [seen, setSeen] = useState(page);
  const [dir, setDir] = useState(1);
  if (page !== seen) {
    setDir(page > seen ? 1 : -1);
    setSeen(page);
  }

  const arrow =
    "flex size-8 items-center justify-center rounded-full text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/60 disabled:pointer-events-none disabled:opacity-35";

  return (
    <nav aria-label="Pagination" className={cn("inline-flex items-center gap-1 rounded-full border border-border bg-card p-1 shadow-xs", className)}>
      <motion.button type="button" aria-label="Previous page" disabled={page <= 1} whileTap={{ x: -2 }} onClick={() => onPageChange(page - 1)} className={arrow}>
        <ChevronLeft className="size-4" />
      </motion.button>

      <p aria-live="polite" aria-atomic className="flex items-center gap-1.5 px-1.5 text-sm text-muted-foreground select-none">
        Page
        <span className="relative inline-flex h-6 min-w-6 items-center justify-center overflow-hidden rounded-md bg-muted px-1.5 font-semibold text-foreground tabular-nums">
          <AnimatePresence mode="popLayout" initial={false} custom={dir}>
            <motion.span
              key={page}
              custom={dir}
              variants={{
                enter: (d: number) => (reduce ? { opacity: 0 } : { y: `${d * 100}%`, opacity: 0 }),
                center: { y: 0, opacity: 1 },
                exit: (d: number) => (reduce ? { opacity: 0 } : { y: `${d * -100}%`, opacity: 0 }),
              }}
              initial="enter"
              animate="center"
              exit="exit"
              transition={{ type: "spring", stiffness: 420, damping: 32 }}
            >
              {page}
            </motion.span>
          </AnimatePresence>
        </span>
        of <span className="font-medium text-foreground tabular-nums">{total}</span>
      </p>

      <motion.button type="button" aria-label="Next page" disabled={page >= total} whileTap={{ x: 2 }} onClick={() => onPageChange(page + 1)} className={arrow}>
        <ChevronRight className="size-4" />
      </motion.button>
    </nav>
  );
}

const first = ["Ines", "Tomas", "Mira", "Jun", "Saoirse", "Kofi", "Lena", "Rafael"];
const last = ["Varga", "Reyl", "Osei", "Okabe", "Byrne", "Lindqvist", "Marchetti"];
const roles = ["Engineering", "Design", "Finance", "Support", "Operations"];
const hues = ["bg-chart-1", "bg-chart-2", "bg-chart-3", "bg-chart-4", "bg-chart-5"];
const PER_PAGE = 4;
const PEOPLE = 46;

const person = (i: number) => ({
  name: `${first[i % first.length]} ${last[(i * 3 + 1) % last.length]}`,
  role: roles[(i * 2) % roles.length],
  hue: hues[i % hues.length],
  seen: `${(i * 7) % 23 || 1}h ago`,
});

export default function PaginationPrevNextCompactDemo() {
  const [page, setPage] = useState(3);
  const total = Math.ceil(PEOPLE / PER_PAGE);
  const start = (page - 1) * PER_PAGE;
  const rows = Array.from({ length: Math.min(PER_PAGE, PEOPLE - start) }, (_, i) => person(start + i));

  return (
    <div className="w-full max-w-md overflow-hidden rounded-2xl border border-border bg-card shadow-sm">
      <div className="border-b border-border px-5 py-3.5">
        <h3 className="text-sm font-semibold text-card-foreground">Members</h3>
      </div>
      <ul className="h-[232px] divide-y divide-border">
        {rows.map((p, i) => (
          <motion.li
            key={`${page}-${i}`}
            initial={{ opacity: 0, y: 6 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.25, delay: i * 0.04 }}
            className="flex h-[58px] items-center gap-3 px-5"
          >
            <span className={cn("flex size-8 items-center justify-center rounded-full text-xs font-semibold text-white", p.hue)}>
              {p.name.split(" ").map((w) => w[0]).join("")}
            </span>
            <div className="min-w-0 flex-1">
              <p className="truncate text-sm font-medium text-card-foreground">{p.name}</p>
              <p className="text-xs text-muted-foreground">{p.role}</p>
            </div>
            <span className="text-xs text-muted-foreground tabular-nums">{p.seen}</span>
          </motion.li>
        ))}
      </ul>
      <div className="flex items-center justify-between border-t border-border bg-muted/40 py-2.5 pr-3 pl-5">
        <p className="text-xs text-muted-foreground tabular-nums">
          {start + 1}–{start + rows.length} of {PEOPLE}
        </p>
        <CompactPager page={page} total={total} onPageChange={setPage} />
      </div>
    </div>
  );
}
```

## AI prompt

```text
Add the "Pagination Prev Next Compact" component from PixelPapa UI to this project.

What it is: A compact previous/next pager with a page-of-total readout whose number rolls up or down in the direction you are paging.
Category: Pagination. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/pagination-prev-next-compact.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/pagination-prev-next-compact.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/pagination-prev-next-compact" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More pagination

- [Pagination Dots Morph](https://pixelpapa.com/components/pagination/pagination-dots-morph.md): Dot pagination where the active dot stretches into a pill with a spring and, on autoplay, fills like a story timer before handing over to the next dot.
- [Pagination Infinite Scroll Sentinel](https://pixelpapa.com/components/pagination/pagination-infinite-scroll-sentinel.md) (Pro): Infinite scrolling driven by an IntersectionObserver sentinel: shimmer rows appear as you near the end, new rows fade up, and a progress hairline tracks how much is loaded.
- [Pagination Jump Input](https://pixelpapa.com/components/pagination/pagination-jump-input.md) (Pro): Pagination built around an editable page field: type a number and press Enter to jump, out-of-range values shake and reset, and a scrubber shows where you are.
- [Pagination Load More](https://pixelpapa.com/components/pagination/pagination-load-more.md) (Pro): A load-more list: the button morphs into a spinner, skeleton rows hold the space, then the new items stagger in and the list scrolls to meet them.
- [Pagination Sliding Active](https://pixelpapa.com/components/pagination/pagination-sliding-active.md): Numbered pagination where the active pill springs between pages; deep in the range the numbers reel through the pill instead, and each ellipsis jumps five pages.
