Components

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.

Updated Jul 17, 2026 by Leonardo Posada

Anatomy

  1. 1
    Pagination

    The <nav aria-label='pagination'> container. Centered mx-auto by default.

  2. 2
    PaginationContent

    The <ul> row holding all items. gap-1 between them.

  3. 3
    PaginationItem

    Wrapper <li> for each element (Previous / number / Ellipsis / Next). Keeps semantics right.

  4. 4
    PaginationLink

    A page number. Renders as Button ghost by default; when isActive it switches to Button outline (border + white surface). Square size-9.

  5. 5
    PaginationPrevious

    Ghost button + CaretLeft leading + label. Set aria-disabled and pointer-events-none on page 1.

  6. 6
    PaginationNext

    Ghost button + label + CaretRight trailing. Set aria-disabled and pointer-events-none on the last page.

  7. 7
    PaginationEllipsis

    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.

Simple (Previous + Next)
<Pagination>
  <PaginationContent>
    <PaginationItem><PaginationPrevious href="#" /></PaginationItem>
    <PaginationItem><PaginationNext href="#" /></PaginationItem>
  </PaginationContent>
</Pagination>
Numbered
<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>
Numbered with ellipsis
<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>
Compact (icons only)
Page 3 of 12
<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.

Default / Active / Disabled
<PaginationLink href="#">1</PaginationLink>            {/* default */}
<PaginationLink href="#" isActive>2</PaginationLink>   {/* active (outline) */}
<PaginationLink href="#" aria-disabled className="pointer-events-none opacity-50">3</PaginationLink>
Previous disabled on page 1
<PaginationPrevious href="#" aria-disabled
  className="pointer-events-none opacity-50" />
Canonical Yuno table footer
Every list/table screen in the Dashboard uses the same footer layout: 'Showing X-Y of Z' left, rows-per-page Select + numbered Pagination right. Reach for the 'Table footer' recipe first — it's the pattern reviewers expect.

Recipes

Ready-to-copy compositions covering the most common Yuno usages of this atom.

Table footer (canonical Dashboard pattern)

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.

Showing 1-25 of 312
Rows per page
<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>
Simple Previous / Next

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>
Numbered with ellipsis

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>
Compact (chevrons + current/total)

Two icon-only chevrons and a 'Page 3 of 12' text between them. Use in mobile-adjacent prototypes or dense sidebars.

Page 3 of 12
<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.

Full compound. For the Table footer recipe also import Select.
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.

PropTypeDefaultDescription
Pagination.classNamestringMerged onto the <nav>. Default is centered (mx-auto w-full justify-center); override to justify-end for table footers.
PaginationLink.hrefstringDestination. Standard anchor — use next/link for client-side navigation.
PaginationLink.isActivebooleanfalseWhen 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.classNamestringMerged. Use pointer-events-none + opacity-50 + aria-disabled when on the first page.
PaginationNext.classNamestringMerged. Same disabled pattern as Previous when on the last page.
PaginationEllipsiscomponentNon-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

Do
  • 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
  • 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.

  • TableThe most common surface Pagination lives below.
  • SelectPair with a small Select for rows-per-page in the table footer.
  • ButtonPagination inherits buttonVariants — customize the base first.