# Pagination

URL: https://ark-ui.com/docs/components/pagination
Source: https://raw.githubusercontent.com/chakra-ui/ark/refs/heads/main/website/src/content/pages/components/pagination.mdx

A navigation component that allows users to browse through pages.

---

<ComponentPreview id="Pagination" />

## Anatomy

<Anatomy id="pagination" />

```tsx
<Pagination.Root>
  <Pagination.PrevTrigger />
  <Pagination.Item />
  <Pagination.Ellipsis />
  <Pagination.NextTrigger />
</Pagination.Root>
```

## Examples

**Example: basic**

```ripple
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component Basic() {
  <Pagination.Root count={5000} pageSize={10} siblingCount={2} class={styles.Root}>
    <div class={styles.Controls}>
      <Pagination.PrevTrigger class={styles.Trigger}>
        <ChevronLeft />
      </Pagination.PrevTrigger>
      <Pagination.Context>
        component children({ context }) {
          for (const [index, page] of @context.pages.entries(); key page.value) {
            if (page.type === 'page') {
              <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
            } else {
              <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
            }
          }
        }
      </Pagination.Context>
      <Pagination.NextTrigger class={styles.Trigger}>
        <ChevronRight />
      </Pagination.NextTrigger>
    </div>
  </Pagination.Root>
}

```

### Controlled

To create a controlled Pagination component, manage the state of the current page using the `page` prop and update it
when the `onPageChange` event handler is called:

**Example: controlled**

```ripple
import { track } from 'ripple';
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component Controlled() {
  let currentPage = track(1);

  <Pagination.Root
    count={5000}
    pageSize={10}
    siblingCount={2}
    page={currentPage}
    onPageChange={(details) => {
      @currentPage = details.page;
    }}
    class={styles.Root}
  >
    <div class={styles.Controls}>
      <Pagination.PrevTrigger class={styles.Trigger}>
        <ChevronLeft />
      </Pagination.PrevTrigger>
      <Pagination.Context>
        component children({ context }) {
          for (const [index, page] of @context.pages.entries(); key page.value) {
            if (page.type === 'page') {
              <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
            } else {
              <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
            }
          }
        }
      </Pagination.Context>
      <Pagination.NextTrigger class={styles.Trigger}>
        <ChevronRight />
      </Pagination.NextTrigger>
    </div>
  </Pagination.Root>
}

```

### Root Provider

An alternative way to control the pagination is to use the `RootProvider` component and the `usePagination` hook. This
way you can access the state and methods from outside the component.

**Example: root-provider**

```ripple
import { Pagination, usePagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component RootProvider() {
  const pagination = usePagination({ count: 5000, pageSize: 10, siblingCount: 2 });

  <div class="stack">
    <button class={styles.Trigger} onClick={() => @pagination.goToNextPage()}>{'Next Page'}</button>

    <Pagination.RootProvider value={pagination} class={styles.Root}>
      <div class={styles.Controls}>
        <Pagination.PrevTrigger class={styles.Trigger}>
          <ChevronLeft />
        </Pagination.PrevTrigger>
        <Pagination.Context>
          component children({ context }) {
            for (const [index, page] of @context.pages.entries(); key page.value) {
              if (page.type === 'page') {
                <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
              } else {
                <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
              }
            }
          }
        </Pagination.Context>
        <Pagination.NextTrigger class={styles.Trigger}>
          <ChevronRight />
        </Pagination.NextTrigger>
      </div>
    </Pagination.RootProvider>
  </div>
}

```

### Customization

You can customize the Pagination component by setting various props such as `dir`, `pageSize`, `siblingCount`, and
`translations`. Here's an example of a customized Pagination:

**Example: customized**

```ripple
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component Customized() {
  <Pagination.Root
    count={5000}
    pageSize={20}
    siblingCount={3}
    translations={{
      nextTriggerLabel: 'Next',
      prevTriggerLabel: 'Prev',
      itemLabel: (details) => `Page ${details.page}`,
    }}
    class={styles.Root}
  >
    <div class={styles.Controls}>
      <Pagination.PrevTrigger class={styles.Trigger}>
        <ChevronLeft />
      </Pagination.PrevTrigger>
      <Pagination.Context>
        component children({ context }) {
          for (const [index, page] of @context.pages.entries(); key page.value) {
            if (page.type === 'page') {
              <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
            } else {
              <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
            }
          }
        }
      </Pagination.Context>
      <Pagination.NextTrigger class={styles.Trigger}>
        <ChevronRight />
      </Pagination.NextTrigger>
    </div>
  </Pagination.Root>
}

```

### Context

Access pagination state and methods with `Pagination.Context` or the `usePaginationContext` hook. You get methods like
`setPage`, `setPageSize`, `goToNextPage`, `goToPrevPage`, `goToFirstPage`, `goToLastPage`, as well as properties like
`totalPages` and `pageRange`.

**Example: context**

```ripple
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component Context() {
  <Pagination.Root count={100} pageSize={10} class={styles.Root}>
    <Pagination.Context>
      component children({ context }) {
        <div class={styles.Controls}>
          <button class={styles.Trigger} onClick={() => @context.goToFirstPage()}>
            <ChevronsLeft />
          </button>
          <button class={styles.Trigger} onClick={() => @context.goToPrevPage()}>
            <ChevronLeft />
          </button>
          <p class={styles.Text} style="min-width: 120px; text-align: center">
            {'Page '}
            {@context.page}
            {' of '}
            {@context.totalPages}
          </p>
          <button class={styles.Trigger} onClick={() => @context.goToNextPage()}>
            <ChevronRight />
          </button>
          <button class={styles.Trigger} onClick={() => @context.goToLastPage()}>
            <ChevronsRight />
          </button>
        </div>
      }
    </Pagination.Context>
  </Pagination.Root>
}

```

### Data Slicing

Use the `slice()` method to paginate actual data arrays. This method automatically slices your data based on the current
page and page size.

**Example: data-slicing**

```ripple
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

const users = [
  { id: 1, name: 'Emma Wilson', email: 'emma@example.com' },
  { id: 2, name: 'Liam Johnson', email: 'liam@example.com' },
  { id: 3, name: 'Olivia Brown', email: 'olivia@example.com' },
  { id: 4, name: 'Noah Davis', email: 'noah@example.com' },
  { id: 5, name: 'Ava Martinez', email: 'ava@example.com' },
  { id: 6, name: 'Ethan Garcia', email: 'ethan@example.com' },
  { id: 7, name: 'Sophia Rodriguez', email: 'sophia@example.com' },
  { id: 8, name: 'Mason Lee', email: 'mason@example.com' },
  { id: 9, name: 'Isabella Walker', email: 'isabella@example.com' },
  { id: 10, name: 'James Hall', email: 'james@example.com' },
  { id: 11, name: 'Mia Allen', email: 'mia@example.com' },
  { id: 12, name: 'Benjamin Young', email: 'benjamin@example.com' },
  { id: 13, name: 'Charlotte King', email: 'charlotte@example.com' },
  { id: 14, name: 'William Wright', email: 'william@example.com' },
  { id: 15, name: 'Amelia Scott', email: 'amelia@example.com' },
  { id: 16, name: 'Henry Green', email: 'henry@example.com' },
  { id: 17, name: 'Harper Adams', email: 'harper@example.com' },
  { id: 18, name: 'Sebastian Baker', email: 'sebastian@example.com' },
  { id: 19, name: 'Evelyn Nelson', email: 'evelyn@example.com' },
  { id: 20, name: 'Jack Carter', email: 'jack@example.com' },
];

export component DataSlicing() {
  <Pagination.Root count={users.length} pageSize={5} class={styles.Root}>
    <Pagination.Context>
      component children({ context }) {
        <div class={styles.Grid}>
          for (const user of @context.slice(users); key user.id) {
            <div class={styles.GridItem}>
              <span class={styles.GridItemTitle}>{user.name}</span>
              <span class={styles.GridItemText}>{user.email}</span>
            </div>
          }
        </div>

        <div class={styles.Controls}>
          <Pagination.PrevTrigger class={styles.Trigger}>
            <ChevronLeft />
          </Pagination.PrevTrigger>

          for (const [index, page] of @context.pages.entries(); key page.value) {
            if (page.type === 'page') {
              <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
            } else {
              <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
            }
          }

          <Pagination.NextTrigger class={styles.Trigger}>
            <ChevronRight />
          </Pagination.NextTrigger>
        </div>
      }
    </Pagination.Context>
  </Pagination.Root>
}

```

### Page Range

Display the current page range information using the `pageRange` property. This shows which items are currently visible
(e.g., "Showing 1-10 of 100 results").

**Example: page-range**

```ripple
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component PageRange() {
  <Pagination.Root count={100} pageSize={10} class={styles.Root}>
    <Pagination.Context>
      component children({ context }) {
        <div class={styles.Controls}>
          <Pagination.PrevTrigger class={styles.Trigger}>
            <ChevronLeft />
          </Pagination.PrevTrigger>

          for (const [index, page] of @context.pages.entries(); key page.value) {
            if (page.type === 'page') {
              <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
            } else {
              <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
            }
          }

          <Pagination.NextTrigger class={styles.Trigger}>
            <ChevronRight />
          </Pagination.NextTrigger>
        </div>

        <div class="stack">
          <p class={styles.Text}>
            {'Showing '}
            {@context.pageRange.start + 1}
            {'-'}
            {@context.pageRange.end}
            {' of '}
            {@context.count}
            {' results'}
          </p>
          <p class={styles.Text}>
            {'Page '}
            {@context.page}
            {' of '}
            {@context.totalPages}
          </p>
        </div>
      }
    </Pagination.Context>
  </Pagination.Root>
}

```

### Page Size

Control the number of items per page dynamically using `setPageSize()`. This example shows how to integrate a native
select element to change the page size.

> **Note:** For uncontrolled behavior, use `defaultPageSize` to set the initial value. For controlled behavior, use
> `pageSize` and `onPageSizeChange` to programmatically manage the page size.

**Example: page-size-control**

```ripple
import { Pagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component PageSizeControl() {
  <Pagination.Root count={100} defaultPageSize={10} class={styles.Root}>
    <Pagination.Context>
      component children({ context }) {
        <div class="hstack">
          <label class={styles.Text}>{'Items per page:'}</label>
          <select
            class={styles.PageSizeSelect}
            value={@context.pageSize}
            onChange={(e) => @context.setPageSize(Number(e.target.value))}
          >
            <option value={5}>{'5'}</option>
            <option value={10}>{'10'}</option>
            <option value={20}>{'20'}</option>
            <option value={50}>{'50'}</option>
          </select>
        </div>

        <div class={styles.Controls}>
          <Pagination.PrevTrigger class={styles.Trigger}>
            <ChevronLeft />
          </Pagination.PrevTrigger>

          for (const [index, page] of @context.pages.entries(); key page.value) {
            if (page.type === 'page') {
              <Pagination.Item {...page} class={styles.Item}>{page.value}</Pagination.Item>
            } else {
              <Pagination.Ellipsis {index} class={styles.Ellipsis}>{'…'}</Pagination.Ellipsis>
            }
          }

          <Pagination.NextTrigger class={styles.Trigger}>
            <ChevronRight />
          </Pagination.NextTrigger>
        </div>

        <p class={styles.Text}>
          {'Page '}
          {@context.page}
          {' of '}
          {@context.totalPages}
        </p>
      }
    </Pagination.Context>
  </Pagination.Root>
}

```

### Links

Create pagination with link navigation for better SEO and accessibility. This example shows how to use the pagination
component with anchor links instead of buttons.

**Example: link**

```ripple
import { Pagination, usePagination } from 'ark-ripple/pagination';
import { ChevronLeft, ChevronRight } from 'lucide-ripple';
import styles from 'styles/pagination.module.css';

export component Link() {
  const pagination = usePagination(
    {
      type: 'link',
      count: 100,
      pageSize: 10,
      siblingCount: 2,
      getPageUrl: ({ page }) => `/page=${page}`,
    },
  );

  <Pagination.RootProvider value={pagination} class={styles.Root}>
    <div class={styles.Controls}>
      <a class={styles.Trigger} {...@pagination.getPrevTriggerProps()}>
        <ChevronLeft />
      </a>
      for (const [index, page] of @pagination.pages.entries(); key page.value) {
        if (page.type === 'page') {
          <a class={styles.Item} {...@pagination.getItemProps(page)}>{page.value}</a>
        } else {
          <span class={styles.Ellipsis} {...@pagination.getEllipsisProps({ index })}>{'…'}</span>
        }
      }
      <a class={styles.Trigger} {...@pagination.getNextTriggerProps()}>
        <ChevronRight />
      </a>
    </div>
  </Pagination.RootProvider>
}

```

## API Reference

### Props

**Component API Reference**

**Root Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `boundaryCount` | `number` | No | Number of pages to show at the beginning and end |
| `count` | `number` | No | Total number of data items |
| `defaultPage` | `number` | No | The initial active page when rendered.
Use when you don't need to control the active page of the pagination. |
| `defaultPageSize` | `number` | No | The initial number of data items per page when rendered.
Use when you don't need to control the page size of the pagination. |
| `getPageUrl` | `(details: PageUrlDetails) => string` | No | Function to generate href attributes for pagination links.
Only used when `type` is set to "link". |
| `ids` | `Partial<{
  root: string
  ellipsis: (index: number) => string
  firstTrigger: string
  prevTrigger: string
  nextTrigger: string
  lastTrigger: string
  item: (page: number) => string
}>` | No | The ids of the elements in the accordion. Useful for composition. |
| `onPageChange` | `(details: PageChangeDetails) => void` | No | Called when the page number is changed |
| `onPageSizeChange` | `(details: PageSizeChangeDetails) => void` | No | Called when the page size is changed |
| `page` | `number` | No | The controlled active page |
| `pageSize` | `number` | No | The controlled number of data items per page |
| `siblingCount` | `number` | No | Number of pages to show beside active page |
| `translations` | `IntlTranslations` | No | Specifies the localized strings that identifies the accessibility elements and their states |
| `type` | `'button' | 'link'` | No | The type of the trigger element |


**Ellipsis Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `index` | `number` | Yes |  |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**FirstTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**FirstTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | pagination |
| `[data-part]` | first-trigger |
| `[data-disabled]` | Present when disabled |


**Item Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `type` | `'page'` | Yes |  |
| `value` | `number` | Yes |  |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**Item Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | pagination |
| `[data-part]` | item |
| `[data-index]` | The index of the item |
| `[data-selected]` | Present when selected |


**LastTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**LastTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | pagination |
| `[data-part]` | last-trigger |
| `[data-disabled]` | Present when disabled |


**NextTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**NextTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | pagination |
| `[data-part]` | next-trigger |
| `[data-disabled]` | Present when disabled |


**PrevTrigger Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


**PrevTrigger Data Attributes:**

| Attribute | Value |
|-----------|-------|
| `[data-scope]` | pagination |
| `[data-part]` | prev-trigger |
| `[data-disabled]` | Present when disabled |


**RootProvider Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `value` | `UsePaginationReturn` | Yes |  |
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |


### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `page` | `number` | The current page. |
| `count` | `number` | The total number of data items. |
| `pageSize` | `number` | The number of data items per page. |
| `totalPages` | `number` | The total number of pages. |
| `pages` | `Pages` | The page range. Represented as an array of page numbers (including ellipsis) |
| `previousPage` | `number` | The previous page. |
| `nextPage` | `number` | The next page. |
| `pageRange` | `PageRange` | The page range. Represented as an object with `start` and `end` properties. |
| `slice` | `<V>(data: V[]) => V[]` | Function to slice an array of data based on the current page. |
| `setPageSize` | `(size: number) => void` | Function to set the page size. |
| `setPage` | `(page: number) => void` | Function to set the current page. |
| `goToNextPage` | `VoidFunction` | Function to go to the next page. |
| `goToPrevPage` | `VoidFunction` | Function to go to the previous page. |
| `goToFirstPage` | `VoidFunction` | Function to go to the first page. |
| `goToLastPage` | `VoidFunction` | Function to go to the last page. |
