# Pagination

Moves between the pages of a list, such as the email log.

- **Import:** `import { Pagination, PaginationContent, PaginationLink, PaginationItem, PaginationPrevious, PaginationNext, PaginationEllipsis } from "@booleanpress/ui/pagination"`
- **APG Landmarks: navigation:** <https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/navigation.html>
- **Page:** <https://ui.booleanpress.com/components/pagination> · @booleanpress/ui 0.1.0

## Usage

Pagination is a navigation landmark holding a list of links. It draws the links and the current page; it does not know how many pages there are. You decide which numbers to show, when to add an ellipsis, and where each link goes.

```tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@booleanpress/ui/pagination"

export function Pages() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem><PaginationPrevious href="?page=1" /></PaginationItem>
        <PaginationItem><PaginationLink href="?page=2" isActive>2</PaginationLink></PaginationItem>
        <PaginationItem><PaginationNext href="?page=3" /></PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}
```

Every part renders a plain `<a>`. In a single-page app, give it an `onClick` that calls `preventDefault` and changes the page (the As buttons example), or render your router's link. `isActive` marks the current page: it draws the outline button and sets `aria-current="page"`. `PaginationLink` takes `size` from the button sizes.

The landmark name and the link labels (`pagination`, `previous`, `next`, `previousPage`, `nextPage`, `morePages`) come from the provider's `strings`, so a translated product sets them once on `BooleanUIProvider`.

## Examples

### Basic

Previous, three numbered pages with the second current, and Next.

```tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@booleanpress/ui/pagination"

export default function PaginationBasic() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#page-1" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-1">1</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-2" isActive>
            2
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-3">3</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#page-3" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}
```

### With ellipsis

A long list shows the first and last page and the pages around the current one; `PaginationEllipsis` stands for the gap.

```tsx
import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@booleanpress/ui/pagination"

export default function PaginationWithEllipsis() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#page-11" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-1">1</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationEllipsis />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-11">11</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-12" isActive>
            12
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-13">13</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationEllipsis />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-40">40</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#page-13" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}
```

### First page

On the first page, Previous is dimmed, skipped by Tab and marked `aria-disabled`.

```tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@booleanpress/ui/pagination"

export default function PaginationFirstAndLastPage() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious aria-disabled="true" tabIndex={-1} className="pointer-events-none opacity-50" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-1" isActive>
            1
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#page-2">2</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#page-2" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}
```

### As buttons

In a single-page app the links change state instead of the address; the current page is announced with `aria-current`.

```tsx
import { useState } from "react"
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@booleanpress/ui/pagination"

const PAGES = [1, 2, 3, 4]

export default function PaginationAsButtons() {
  const [page, setPage] = useState(2)
  const go = (next: number) => (event: React.MouseEvent) => {
    event.preventDefault()
    setPage(Math.min(PAGES.length, Math.max(1, next)))
  }

  return (
    <div className="flex flex-col items-center gap-3">
      <p className="text-sm text-muted-foreground">Showing log page {page} of {PAGES.length}</p>
      <Pagination>
        <PaginationContent>
          <PaginationItem>
            <PaginationPrevious href="#" onClick={go(page - 1)} />
          </PaginationItem>
          {PAGES.map((n) => (
            <PaginationItem key={n}>
              <PaginationLink href="#" isActive={n === page} onClick={go(n)}>
                {n}
              </PaginationLink>
            </PaginationItem>
          ))}
          <PaginationItem>
            <PaginationNext href="#" onClick={go(page + 1)} />
          </PaginationItem>
        </PaginationContent>
      </Pagination>
    </div>
  )
}
```

## Accessibility

**Semantics.** A `nav` landmark named by the `pagination` string, holding a `ul`. The current page is a link with `aria-current="page"`. Previous and Next carry the `previousPage` and `nextPage` strings as `aria-label`, and the ellipsis is hidden from assistive technology.

**Labels.** A page needs a name that says what it is: a number alone is read as "2, link". Add `aria-label="Page 2"` to a number if the list around it does not make the meaning clear. If a page has more than one pagination, give each its own `aria-label`, because the strings give every one the same name.

**Focus.** Every link is a tab stop. The focus ring shows on keyboard focus. A link with no `href` is not a tab stop.

**Known limits.**

- There is no disabled state. For the first or last page, dim the link, remove it from the tab order (`tabIndex={-1}`) and set `aria-disabled="true"` yourself, as the First page example does, or leave the link out.
- Previous and Next show only a chevron below the `sm` breakpoint; their accessible name stays.
- A change of page is not announced. Move focus to the list heading, or announce the new page in a live region.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Moves focus to the next link. |
| Shift + Tab | Moves focus to the previous link. |
| Enter | Follows the focused link. |

## API

### Pagination

Renders a `nav` and passes it every other prop.

### PaginationContent

Renders a `ul` and passes it every other prop.

### PaginationLink

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `isActive` | `boolean` |  | Marks the current page: the outline style and `aria-current="page"`. |

### PaginationItem

Renders a `li` and passes it every other prop.

### PaginationPrevious

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `isActive` | `boolean` |  |  |

### PaginationNext

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `isActive` | `boolean` |  |  |

### PaginationEllipsis

Renders a `span` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="pagination"` (Pagination), `data-slot="pagination-content"` (PaginationContent), `data-slot="pagination-link"` (PaginationLink), `data-slot="pagination-item"` (PaginationItem), `data-slot="pagination-ellipsis"` (PaginationEllipsis), and `data-active`.

**Provider strings:** `pagination`, `previousPage`, `previous`, `nextPage`, `next`, `morePages` (`BooleanUIProvider`'s `strings`).

## Theming

The current page uses the `outline` button, the others `ghost`. The chevrons turn around in a right-to-left page, so Previous always points the way a reader goes back.
