# Pagination

> Previous, next, and numbered page links in a labeled nav, with the current page marked and an ellipsis for gaps.

- Name: `pagination`
- Status: beta
- Categories: navigation
- File: the install writes `pagination.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/button-variants`. The install adds them.
- Gallery: https://arbokui.com/components/pagination
- Registry item: https://arbokui.com/r/pagination.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/pagination
   ```

## Use it for

- Moving through a long list split into pages, like search results, orders, or a table.
- Mark the page people are on with current. It sets aria-current="page" and draws an outline.
- Keep the first and last page, the pages beside the current one, and a PaginationEllipsis for each gap.
- Set disabled on Previous on the first page and on Next on the last, so the row keeps its shape.
- Put the page in the URL, like ?page=3, so people can share it and the back button works.
- With a router, pass its link as render: \<PaginationLink render={\<Link href="?page=2" />}>2\</PaginationLink>.
- Set the same size on every link and the ellipsis in one row.

## Avoid it for

- Short lists that fit on one screen. Show them all.
- Feeds people scroll through without a goal. A Load more button keeps their place better.
- Steps of a form or a flow. Use StepDots, or Back and Next buttons.
- Showing every page of a long list. Phones wrap the row. Show about seven items at most.
- Two navs named "Pagination" on one page, like above and below a table. Pass a different aria-label to each.

## Props

- `size`: `"sm" | "md" | "lg"`. Default `"md"`. On PaginationLink, PaginationPrevious, PaginationNext, and PaginationEllipsis. Height and width. Density scales them.
- `current`: `boolean`. Default `false`. On PaginationLink. The page people are on. Sets aria-current="page" and an outline.
- `disabled`: `boolean`. Default `false`. On PaginationLink, PaginationPrevious, and PaginationNext. Drops href and onClick, sets aria-disabled, and dims the link. It is not a tab stop.
- `aria-label`: `string`. Default `"Pagination"`. On Pagination. The landmark's name. Rename it when a page has more than one. Left off when you pass aria-labelledby.
- `label`: `string`. Default `"More pages"`. On PaginationEllipsis. What screen readers hear in place of the dots.
- `render`: `ReactElement`. On PaginationLink, PaginationPrevious, and PaginationNext. Renders your element, like a router link, with the link's props and classes. A disabled link ignores it and renders an \<a>.
- `Pagination`: `Omit<ComponentProps<"nav">, "color">`. The landmark. A \<nav> with aria-label="Pagination". Pass aria-label or aria-labelledby to rename it.
- `PaginationContent`: `Omit<ComponentProps<"ul">, "color">`. Part. The list of items. It wraps as a last resort, so it never pushes the page sideways.
- `PaginationItem`: `Omit<ComponentProps<"li">, "color">`. Part. One item in the row.
- `PaginationLink`: `Omit<ComponentProps<"a">, "color"> & { current?, disabled?, size?, render? }`. Part. One page, styled with buttonVariants: ghost, or outline when current. At least as wide as it is tall.
- `PaginationPrevious`: `Omit<ComponentProps<"a">, "color"> & { disabled?, size?, render? }`. Part. Named "Previous page". Below the sm breakpoint the words are visually hidden and the link turns square.
- `PaginationNext`: `Omit<ComponentProps<"a">, "color"> & { disabled?, size?, render? }`. Part. Named "Next page". Below the sm breakpoint the words are visually hidden and the link turns square.
- `PaginationEllipsis`: `Omit<ComponentProps<"span">, "children" | "color"> & { label?, size? }`. Part. Three dots for pages left out. label is what screen readers hear. Default "More pages".
- `className`: `string`. Extra classes on any part. Later classes win, so className='rounded-full' on PaginationLink replaces the radius.

## Accessibility

- Pagination renders a \<nav> named "Pagination", so screen readers list it with the page's landmarks.
- The items are a \<ul>, so screen readers say how many there are.
- current sets aria-current="page". The current page also gets an outline, so it is marked by shape, not only by color.
- Previous and Next are named "Previous page" and "Next page". On narrow screens the words are visually hidden but still read. Their chevrons are aria-hidden.
- A disabled link has no href, so it is not a tab stop. It keeps role="link" and aria-disabled="true", so screen readers still say it is there and unavailable.
- PaginationEllipsis has visually hidden text, "More pages" by default. Its dots are aria-hidden, and it is not interactive.
- Links are native \<a> elements, so Tab moves between them and Enter follows them.
- Link text uses the tone's soft foreground, held to 4.5:1 on the page. The current page's outline is held to 3:1, and so is the focus ring.

## Examples

### Page 1 of 10

```tsx
<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious disabled />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="?page=1" current>
        1
      </PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="?page=2">2</PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="?page=3">3</PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationEllipsis />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="?page=10">10</PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext href="?page=2" />
    </PaginationItem>
  </PaginationContent>
</Pagination>
```

### With Next.js links

```tsx
import Link from "next/link";

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious render={<Link href="?page=4" />} />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink render={<Link href="?page=5" />} current>
        5
      </PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext render={<Link href="?page=6" />} />
    </PaginationItem>
  </PaginationContent>
</Pagination>
```

### A row built from the current page

```tsx
import { Fragment } from "react";

function Pages({ page, total }: { page: number; total: number }) {
  const shown = [...new Set([1, page - 1, page, page + 1, total])]
    .filter((n) => n >= 1 && n <= total)
    .sort((a, b) => a - b);
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href={`?page=${page - 1}`} disabled={page === 1} />
        </PaginationItem>
        {shown.map((n, i) => (
          <Fragment key={n}>
            {i > 0 && n - shown[i - 1] > 1 ? (
              <PaginationItem>
                <PaginationEllipsis />
              </PaginationItem>
            ) : null}
            <PaginationItem>
              <PaginationLink href={`?page=${n}`} current={n === page}>
                {n}
              </PaginationLink>
            </PaginationItem>
          </Fragment>
        ))}
        <PaginationItem>
          <PaginationNext href={`?page=${page + 1}`} disabled={page === total} />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}
```

### Above and below a table

```tsx
<Pagination aria-label="Pages, top">
  {/* ... */}
</Pagination>
{/* the table */}
<Pagination aria-label="Pages, bottom">
  {/* ... */}
</Pagination>
```
