---
title: Migrate Pagination from Polaris React
description: >-
  Replace Polaris React Pagination with s-table pagination or explicit previous
  and next navigation backed by the same query state as the results.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/pagination
  md: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/pagination.md
api_name: app-home
---

# Migrate Pagination from Polaris React

Polaris web components don't provide a standalone `Pagination` component. Use built-in [`s-table`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/table) pagination for table results. For other content, compose visible previous and next navigation and keep it backed by the same URL and backend query state as the displayed results.

***

## Migrate non-table pagination

Use real URLs when each page has a stable destination. This preserves direct loads, modified clicks, copied links, and browser history.

## Migrating article pagination

##### Polaris web components

```tsx
function ArticlePagination({previousUrl, nextUrl, label}) {
  return (
    <nav aria-label="Article pagination">
      <s-stack
        direction="inline"
        gap="small"
        alignItems="center"
        justifyContent="center"
      >
        <s-button
          icon="chevron-left"
          href={previousUrl}
          disabled={!previousUrl}
        >
          Previous
        </s-button>
        <s-box paddingInline="small">
          <s-text>{label}</s-text>
        </s-box>
        <s-button
          icon="chevron-right"
          href={nextUrl}
          disabled={!nextUrl}
        >
          Next
        </s-button>
      </s-stack>
    </nav>
  );
}
```

##### Polaris React

```tsx
import {Pagination} from '@shopify/polaris';

export function ArticlePagination({previousUrl, nextUrl, label}) {
  return (
    <Pagination
      accessibilityLabel="Article pagination"
      previousURL={previousUrl}
      nextURL={nextUrl}
      hasPrevious={Boolean(previousUrl)}
      hasNext={Boolean(nextUrl)}
      label={label}
    />
  );
}
```

***

## Use s-table pagination for table data

Set `paginate` on `s-table`, derive `hasPreviousPage` and `hasNextPage` from backend page information, and handle `onPreviousPage` and `onNextPage`. Keep loading, sort, filters, cursor, and visible rows in one data-view model.

Reset the cursor whenever search, filters, or sorting changes. A cursor from an earlier query doesn't identify a page in the new result set. See the [DataTable migration](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/data-table) for a complete example.

***

## Map pagination behavior

| Polaris React | Polaris web components | Migration notes |
| - | - | - |
| `previousURL` and `nextURL` | `href` on previous and next controls | Keep real URLs when pages are addressable. |
| `onPrevious` and `onNext` | `onClick` or table page events | Use callbacks for app-owned cursor requests that don't have a link destination. |
| `hasPrevious` and `hasNext` | `disabled` for composed controls, or `hasPreviousPage` and `hasNextPage` on `s-table` | Derive availability from the current backend response. |
| `label` | Visible `s-text` between controls | Keep useful context such as **Page 2 of 5** when the total is reliable. |
| `accessibilityLabel` and button labels | Visible labels plus `accessibilityLabel` when extra context is needed | Don't rely on arrow icons alone. |
| `type="table"` | `paginate` on `s-table` | Keep the controls with the table they update. |
| `nextTooltip` and `previousTooltip` | Visible button text | A tooltip isn't a substitute for the control's accessible name. |
| `nextKeys` and `previousKeys` | Remove in most apps | Don't register global shortcuts that conflict with browser or admin behavior. |

***

## Keep query and navigation state together

For cursor pagination, store the cursor and direction in URL search parameters when the result page should restore after reload or browser navigation. Keep the active search, filters, and sort values in that same URL. For numbered pages, validate requested page bounds on the server.

While a page request is pending, preserve the current results or render an intentional loading state, and block duplicate page requests. After results load, move focus only when needed to keep the merchant oriented; don't force focus on every background refresh.

***

## Test the migration

* Navigate to first, middle, and last pages and verify control availability.
* Reload, use back and forward navigation, and open a page URL directly.
* Change search, filters, or sort from a later page and verify pagination resets.
* Test slow, failed, duplicate, and out-of-order page requests.
* Verify table and non-table controls have visible, specific names and keyboard focus.
* Confirm empty or deleted result pages recover to a valid location.

***

## Remove Polaris React

After every call site is migrated, remove `Pagination`, keyboard-shortcut helpers used only by it, and duplicate page state. Remove `@shopify/polaris` only after no other route in scope imports it.

***

## Related guidance

* [Table component](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/table)
* [Migrate DataTable from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/data-table)
* [Migrate IndexTable from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/index-table)

***
