Ark UI Logo
Components
Pagination

Pagination

A navigation component that allows users to browse through pages.

Loading...

Anatomy

To set up the pagination correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Examples

Learn how to use the Pagination component in your project. Let's take a look at the most basic example:

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

export const Basic = () => (
  <Pagination.Root count={5000} pageSize={10} siblingCount={2} className={styles.Root}>
    <div className={styles.Controls}>
      <Pagination.PrevTrigger className={styles.Trigger}>
        <ChevronLeftIcon />
      </Pagination.PrevTrigger>
      <Pagination.Context>
        {(pagination) =>
          pagination.pages.map((page, index) =>
            page.type === 'page' ? (
              <Pagination.Item key={index} {...page} className={styles.Item}>
                {page.value}
              </Pagination.Item>
            ) : (
              <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                &#8230;
              </Pagination.Ellipsis>
            ),
          )
        }
      </Pagination.Context>
      <Pagination.NextTrigger className={styles.Trigger}>
        <ChevronRightIcon />
      </Pagination.NextTrigger>
    </div>
  </Pagination.Root>
)

Controlled Pagination

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:

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { useState } from 'react'
import { Pagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

export const Controlled = () => {
  const [currentPage, setCurrentPage] = useState(1)

  return (
    <Pagination.Root
      count={5000}
      pageSize={10}
      siblingCount={2}
      page={currentPage}
      onPageChange={(details) => setCurrentPage(details.page)}
      className={styles.Root}
    >
      <div className={styles.Controls}>
        <Pagination.PrevTrigger className={styles.Trigger}>
          <ChevronLeftIcon />
        </Pagination.PrevTrigger>
        <Pagination.Context>
          {(pagination) =>
            pagination.pages.map((page, index) =>
              page.type === 'page' ? (
                <Pagination.Item key={index} {...page} className={styles.Item}>
                  {page.value}
                </Pagination.Item>
              ) : (
                <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                  &#8230;
                </Pagination.Ellipsis>
              ),
            )
          }
        </Pagination.Context>
        <Pagination.NextTrigger className={styles.Trigger}>
          <ChevronRightIcon />
        </Pagination.NextTrigger>
      </div>
    </Pagination.Root>
  )
}

Customizing Pagination

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:

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

export const Customized = () => (
  <Pagination.Root
    count={5000}
    pageSize={20}
    siblingCount={3}
    translations={{
      nextTriggerLabel: 'Next',
      prevTriggerLabel: 'Prev',
      itemLabel: (details) => `Page ${details.page}`,
    }}
    className={styles.Root}
  >
    <div className={styles.Controls}>
      <Pagination.PrevTrigger className={styles.Trigger}>
        <ChevronLeftIcon />
      </Pagination.PrevTrigger>
      <Pagination.Context>
        {(pagination) =>
          pagination.pages.map((page, index) =>
            page.type === 'page' ? (
              <Pagination.Item key={index} {...page} className={styles.Item}>
                {page.value}
              </Pagination.Item>
            ) : (
              <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                &#8230;
              </Pagination.Ellipsis>
            ),
          )
        }
      </Pagination.Context>
      <Pagination.NextTrigger className={styles.Trigger}>
        <ChevronRightIcon />
      </Pagination.NextTrigger>
    </div>
  </Pagination.Root>
)

Using Context

The Context component provides access to the pagination state and methods through a render prop pattern. This allows you to access methods like setPage, setPageSize, goToNextPage, goToPrevPage, goToFirstPage, goToLastPage, as well as properties like totalPages and pageRange.

import { ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon } from 'lucide-react'
import { Pagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

export const Context = () => {
  return (
    <Pagination.Root count={100} pageSize={10} className={styles.Root}>
      <Pagination.Context>
        {(pagination) => (
          <div className={styles.Controls}>
            <button className={styles.Trigger} onClick={() => pagination.goToFirstPage()}>
              <ChevronsLeftIcon />
            </button>
            <button className={styles.Trigger} onClick={() => pagination.goToPrevPage()}>
              <ChevronLeftIcon />
            </button>
            <p className={styles.Text} style={{ minWidth: '120px', textAlign: 'center' }}>
              Page {pagination.page} of {pagination.totalPages}
            </p>
            <button className={styles.Trigger} onClick={() => pagination.goToNextPage()}>
              <ChevronRightIcon />
            </button>
            <button className={styles.Trigger} onClick={() => pagination.goToLastPage()}>
              <ChevronsRightIcon />
            </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.

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination } from '@ark-ui/react/pagination'
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 const DataSlicing = () => {
  return (
    <Pagination.Root count={users.length} pageSize={5} className={styles.Root}>
      <Pagination.Context>
        {(pagination) => (
          <>
            <div className={styles.Grid}>
              {pagination.slice(users).map((user) => (
                <div key={user.id} className={styles.GridItem}>
                  <span className={styles.GridItemTitle}>{user.name}</span>
                  <span className={styles.GridItemText}>{user.email}</span>
                </div>
              ))}
            </div>

            <div className={styles.Controls}>
              <Pagination.PrevTrigger className={styles.Trigger}>
                <ChevronLeftIcon />
              </Pagination.PrevTrigger>

              {pagination.pages.map((page, index) =>
                page.type === 'page' ? (
                  <Pagination.Item key={index} {...page} className={styles.Item}>
                    {page.value}
                  </Pagination.Item>
                ) : (
                  <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                    &#8230;
                  </Pagination.Ellipsis>
                ),
              )}

              <Pagination.NextTrigger className={styles.Trigger}>
                <ChevronRightIcon />
              </Pagination.NextTrigger>
            </div>
          </>
        )}
      </Pagination.Context>
    </Pagination.Root>
  )
}

Page Range Display

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").

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

export const PageRange = () => {
  return (
    <Pagination.Root count={100} pageSize={10} className={styles.Root}>
      <Pagination.Context>
        {(pagination) => (
          <>
            <div className={styles.Controls}>
              <Pagination.PrevTrigger className={styles.Trigger}>
                <ChevronLeftIcon />
              </Pagination.PrevTrigger>

              {pagination.pages.map((page, index) =>
                page.type === 'page' ? (
                  <Pagination.Item key={index} {...page} className={styles.Item}>
                    {page.value}
                  </Pagination.Item>
                ) : (
                  <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                    &#8230;
                  </Pagination.Ellipsis>
                ),
              )}

              <Pagination.NextTrigger className={styles.Trigger}>
                <ChevronRightIcon />
              </Pagination.NextTrigger>
            </div>

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

Page Size Control

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.

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

export const PageSizeControl = () => {
  return (
    <Pagination.Root count={100} defaultPageSize={10} className={styles.Root}>
      <Pagination.Context>
        {(pagination) => (
          <>
            <div className="hstack">
              <label className={styles.Text}>Items per page:</label>
              <select
                className={styles.PageSizeSelect}
                value={pagination.pageSize}
                onChange={(e) => pagination.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 className={styles.Controls}>
              <Pagination.PrevTrigger className={styles.Trigger}>
                <ChevronLeftIcon />
              </Pagination.PrevTrigger>

              {pagination.pages.map((page, index) =>
                page.type === 'page' ? (
                  <Pagination.Item key={index} {...page} className={styles.Item}>
                    {page.value}
                  </Pagination.Item>
                ) : (
                  <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                    &#8230;
                  </Pagination.Ellipsis>
                ),
              )}

              <Pagination.NextTrigger className={styles.Trigger}>
                <ChevronRightIcon />
              </Pagination.NextTrigger>
            </div>

            <p className={styles.Text}>
              Page {pagination.page} of {pagination.totalPages}
            </p>
          </>
        )}
      </Pagination.Context>
    </Pagination.Root>
  )
}

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.

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination, usePagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

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

  return (
    <Pagination.RootProvider value={pagination} className={styles.Root}>
      <div className={styles.Controls}>
        <a className={styles.Trigger} {...pagination.getPrevTriggerProps()}>
          <ChevronLeftIcon />
        </a>
        {pagination.pages.map((page, index) =>
          page.type === 'page' ? (
            <a key={index} className={styles.Item} {...pagination.getItemProps(page)}>
              {page.value}
            </a>
          ) : (
            <span key={index} className={styles.Ellipsis} {...pagination.getEllipsisProps({ index })}>
              &#8230;
            </span>
          ),
        )}
        <a className={styles.Trigger} {...pagination.getNextTriggerProps()}>
          <ChevronRightIcon />
        </a>
      </div>
    </Pagination.RootProvider>
  )
}

Root Provider

The RootProvider component provides a context for the pagination. It accepts the value of the usePagination hook. You can leverage it to access the component state and methods from outside the pagination.

import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
import { Pagination, usePagination } from '@ark-ui/react/pagination'
import styles from 'styles/pagination.module.css'

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

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

      <Pagination.RootProvider value={pagination} className={styles.Root}>
        <div className={styles.Controls}>
          <Pagination.PrevTrigger className={styles.Trigger}>
            <ChevronLeftIcon />
          </Pagination.PrevTrigger>
          <Pagination.Context>
            {(pagination) =>
              pagination.pages.map((page, index) =>
                page.type === 'page' ? (
                  <Pagination.Item key={index} {...page} className={styles.Item}>
                    {page.value}
                  </Pagination.Item>
                ) : (
                  <Pagination.Ellipsis key={index} index={index} className={styles.Ellipsis}>
                    &#8230;
                  </Pagination.Ellipsis>
                ),
              )
            }
          </Pagination.Context>
          <Pagination.NextTrigger className={styles.Trigger}>
            <ChevronRightIcon />
          </Pagination.NextTrigger>
        </div>
      </Pagination.RootProvider>
    </div>
  )
}

If you're using the RootProvider component, you don't need to use the Root component.

API Reference

Props

Root

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
count
number

Total number of data items

defaultPage1
number

The initial active page when rendered. Use when you don't need to control the active page of the pagination.

defaultPageSize10
number

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

Function to generate href attributes for pagination links. Only used when `type` is set to "link".

ids
Partial<{ root: string ellipsis: (index: number) => string prevTrigger: string nextTrigger: string item: (page: number) => string }>

The ids of the elements in the accordion. Useful for composition.

onPageChange
(details: PageChangeDetails) => void

Called when the page number is changed

onPageSizeChange
(details: PageSizeChangeDetails) => void

Called when the page size is changed

page
number

The controlled active page

pageSize
number

The controlled number of data items per page

siblingCount1
number

Number of pages to show beside active page

translations
IntlTranslations

Specifies the localized strings that identifies the accessibility elements and their states

type'button'
'button' | 'link'

The type of the trigger element

Ellipsis

PropDefaultType
index
number

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

Item

PropDefaultType
type
'page'

value
number

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]pagination
[data-part]item
[data-index]The index of the item
[data-selected]Present when selected

NextTrigger

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]pagination
[data-part]next-trigger
[data-disabled]Present when disabled

PrevTrigger

PropDefaultType
asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.
Data AttributeValue
[data-scope]pagination
[data-part]prev-trigger
[data-disabled]Present when disabled

RootProvider

PropDefaultType
value
UsePaginationReturn

asChild
boolean

Use the provided child element as the default rendered element, combining their props and behavior.

For more details, read our Composition guide.

Context

These are the properties available when using Pagination.Context, usePaginationContext hook or usePagination hook.

API

PropertyType
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.