Pagination
Compound API for page navigation (Previous / numbered pages / Ellipsis / Next). Builds on buttonVariants so numbers match Yuno's outline (active) and ghost (rest) styles. Buttons are h-9 size-9 (36×36) for numbers; Previous/Next are h-9 with a leading/trailing CaretLeft/CaretRight. Used below every table in the Yuno Dashboard.
Anatomy
- 1Pagination
The <nav aria-label='pagination'> container. Centered mx-auto by default.
- 2PaginationContent
The <ul> row holding all items. gap-1 between them.
- 3PaginationItem
Wrapper <li> for each element (Previous / number / Ellipsis / Next). Keeps semantics right.
- 4PaginationLink
A page number. Renders as Button ghost by default; when isActive it switches to Button outline (border + white surface). Square size-9.
- 5PaginationPrevious
Ghost button + CaretLeft leading + label. Set aria-disabled and pointer-events-none on page 1.
- 6PaginationNext
Ghost button + label + CaretRight trailing. Set aria-disabled and pointer-events-none on the last page.
- 7PaginationEllipsis
Three-dot placeholder (DotsThreeOutline light, size-9). Aria-hidden with sr-only 'More pages'.
Variants
Two visual styles for numbers: ghost (default, no border) and outline (active, border + shadow). Previous/Next always ghost. Ellipsis is not interactive.
<Pagination>
<PaginationContent>
<PaginationItem><PaginationPrevious href="#" /></PaginationItem>
<PaginationItem><PaginationNext href="#" /></PaginationItem>
</PaginationContent>
</Pagination><Pagination>
<PaginationContent>
<PaginationItem><PaginationPrevious href="#" /></PaginationItem>
<PaginationItem><PaginationLink href="#">1</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#" isActive>2</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#">3</PaginationLink></PaginationItem>
<PaginationItem><PaginationNext href="#" /></PaginationItem>
</PaginationContent>
</Pagination><Pagination>
<PaginationContent>
<PaginationItem><PaginationPrevious href="#" /></PaginationItem>
<PaginationItem><PaginationLink href="#">1</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#" isActive>4</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#">5</PaginationLink></PaginationItem>
<PaginationItem><PaginationEllipsis /></PaginationItem>
<PaginationItem><PaginationLink href="#">12</PaginationLink></PaginationItem>
<PaginationItem><PaginationNext href="#" /></PaginationItem>
</PaginationContent>
</Pagination><div className="flex items-center gap-3">
<Button variant="ghost" size="icon" aria-label="Previous page">
<CaretLeft weight="light" />
</Button>
<span className="text-sm text-muted-foreground">Page 3 of 12</span>
<Button variant="ghost" size="icon" aria-label="Next page">
<CaretRight weight="light" />
</Button>
</div>States
Number states: default (ghost) / hover (bg-accent + text-accent-foreground) / active (outline variant with border-input + shadow-xs from Button) / focus (ring-2 ring-ring ring-offset-2) / disabled (opacity-50, pointer-events-none). Previous/Next follow the same ghost states.
<PaginationLink href="#">1</PaginationLink> {/* default */}
<PaginationLink href="#" isActive>2</PaginationLink> {/* active (outline) */}
<PaginationLink href="#" aria-disabled className="pointer-events-none opacity-50">3</PaginationLink><PaginationPrevious href="#" aria-disabled
className="pointer-events-none opacity-50" />Recipes
Ready-to-copy compositions covering the most common Yuno usages of this atom.
The full pattern shown below every table in the Yuno Dashboard: 'Showing X-Y of Z' on the left, a Select for rows-per-page + numbered Pagination on the right. Use this exact composition for consistency.
<div className="flex flex-wrap items-center justify-between gap-4 border-t bg-card px-4 py-3">
<div className="text-sm text-muted-foreground">
Showing <span className="font-medium text-foreground">1-25</span> of <span className="font-medium text-foreground">312</span>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">Rows per page</span>
<Select defaultValue="25">
<SelectTrigger className="h-8 w-20"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
</SelectContent>
</Select>
</div>
<Pagination className="mx-0 w-auto justify-end">
<PaginationContent>
<PaginationItem><PaginationPrevious href="#" /></PaginationItem>
<PaginationItem><PaginationLink href="#" isActive>1</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#">2</PaginationLink></PaginationItem>
<PaginationItem><PaginationEllipsis /></PaginationItem>
<PaginationItem><PaginationLink href="#">13</PaginationLink></PaginationItem>
<PaginationItem><PaginationNext href="#" /></PaginationItem>
</PaginationContent>
</Pagination>
</div>
</div>Just two buttons — no numbers. Use when the dataset is tiny (2-5 pages) or the user only needs to move one step at a time.
<Pagination>
<PaginationContent>
<PaginationItem><PaginationPrevious href="#" /></PaginationItem>
<PaginationItem><PaginationNext href="#" /></PaginationItem>
</PaginationContent>
</Pagination>The mid-size default: first + few around current + ellipsis + last. Use for datasets with more than 7 pages.
<Pagination>
<PaginationContent>
<PaginationItem><PaginationPrevious href="#" /></PaginationItem>
<PaginationItem><PaginationLink href="#">1</PaginationLink></PaginationItem>
<PaginationItem><PaginationEllipsis /></PaginationItem>
<PaginationItem><PaginationLink href="#">6</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#" isActive>7</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#">8</PaginationLink></PaginationItem>
<PaginationItem><PaginationEllipsis /></PaginationItem>
<PaginationItem><PaginationLink href="#">20</PaginationLink></PaginationItem>
<PaginationItem><PaginationNext href="#" /></PaginationItem>
</PaginationContent>
</Pagination>Two icon-only chevrons and a 'Page 3 of 12' text between them. Use in mobile-adjacent prototypes or dense sidebars.
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" aria-label="Previous page">
<CaretLeft weight="light" />
</Button>
<span className="text-sm text-muted-foreground">Page 3 of 12</span>
<Button variant="ghost" size="icon" aria-label="Next page">
<CaretRight weight="light" />
</Button>
</div>Import
Copy this import line at the top of the file where you compose this atom.
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";Props
Everything else from the underlying HTML or Radix primitive is forwarded via ...props.
| Prop | Type | Default | Description |
|---|---|---|---|
| Pagination.className | string | — | Merged onto the <nav>. Default is centered (mx-auto w-full justify-center); override to justify-end for table footers. |
| PaginationLink.href | string | — | Destination. Standard anchor — use next/link for client-side navigation. |
| PaginationLink.isActive | boolean | false | When true, renders as Button outline + sets aria-current='page'. Everything else stays ghost. |
| PaginationLink.size | "default" | "sm" | "lg" | "icon" | "icon" | Forwarded to buttonVariants. Numbers use icon (square 36×36); Previous/Next override to default. |
| PaginationPrevious.className | string | — | Merged. Use pointer-events-none + opacity-50 + aria-disabled when on the first page. |
| PaginationNext.className | string | — | Merged. Same disabled pattern as Previous when on the last page. |
| PaginationEllipsis | component | — | Non-interactive 3-dot placeholder (aria-hidden). Wraps 'More pages' in sr-only for screen readers. |
When to use
- Every table or list that returns more than ~20 rows.
- Reports paginated by the backend.
- Any dataset where the user needs to know 'how many pages are there' and 'where am I'.
When not to use
- Infinite-scroll lists — swap for a 'Load more' button.
- Small lists that fit on one screen (5-10 items).
- Live-updating streams where page numbers don't map to stable content.
Usage
- Show the active page as variant='outline'; the rest as ghost.
- Include Previous/Next even when disabled — the shape of the control should stay stable.
- Use Ellipsis when there are more than 7 pages so numbers stay scannable.
- Anchor Pagination to the right of the table footer; put 'Showing X-Y of Z' on the left.
- Don't show every page number when there are more than 7 — use Ellipsis.
- Don't hide Previous/Next on the first/last page — disable them instead.
- Don't center Pagination inside a Card that also has left-aligned totals — use the table footer layout.
- Don't invent a new active style (bg-primary etc.) — keep the outline variant.
Related
Cross-links to atoms and patterns you may reach for next.