---
title: Migrate from Polaris React to Polaris web components
description: >-
  Migrate an iframe-based embedded app from @shopify/polaris to Polaris web
  components safely and incrementally.
source_url:
  html: 'https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react'
  md: 'https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react.md'
api_name: app-home
---

# Migrate from Polaris React to Polaris web components

This guide is for developers who maintain an iframe-based embedded app that uses `@shopify/polaris`. You'll add Polaris web components, migrate the app in working slices, replace app-shell integrations with App Bridge, update tests, and optionally remove the Polaris React package.

Polaris React and Polaris web components can render in the same app while you migrate. The target end state is an embedded interface built with web components and App Home patterns, but you can retain `@shopify/polaris` while a remaining route or dependency still needs it.

***

## Migrate using AI

Use the [Shopify AI Toolkit](https://shopify.dev/docs/apps/build/ai-toolkit) to give your coding agent current Polaris web component, App Bridge, and App Home pattern guidance:

1. Install the Shopify AI Toolkit for your preferred coding agent.

2. Ask the agent to migrate one route or self-contained feature at a time. For example:

   ## Example prompt

   ```text
   Migrate the embedded app route at app/routes/products.tsx from @shopify/polaris to Polaris web components. Preserve its behavior, use App Home patterns where no direct component exists, and verify the result against the Polaris React migration guide.
   ```

3. Review the diff and test the migrated route in an embedded development store before starting the next slice. Don't ask an agent to replace every component across the app in one unreviewed change.

***

## Requirements

* Use an [iframe-based App Home app](https://shopify.dev/docs/apps/build/app-home#build-with-the-iframe-model).
* If the app renders Polaris web components through React, upgrade to React 19 before migrating controlled fields. React 18 doesn't provide the custom-element property and event behavior that controlled Polaris web components rely on.
* Use the current [App Bridge library](https://shopify.dev/docs/api/app-home) and confirm that the `shopify` global is available in the embedded app.
* Inventory the app's `@shopify/polaris` version, test framework, client-side router, and routes that render outside the Shopify admin.
* Find packages that render Polaris React components internally. You can't remove `@shopify/polaris` until those packages are migrated or replaced.
* Find CSS selectors that target `.Polaris-*` classes and custom styles derived from Polaris tokens. Shadow DOM prevents those overrides from styling web components.

App-owned CSS can still style native HTML and wrappers that your app renders, such as a constrained scrolling region, a sticky app-owned element, or an ellipsis treatment. It can also size and position a web component host where ordinary layout rules apply. It can't reach inside a Polaris web component's shadow root, target its internal classes, or replace component styling with `.Polaris-*` selectors. Use documented component properties for the component itself, and keep custom CSS scoped to app-owned elements around it.

The component mappings in this guide use Polaris React `13.9.5` as the comparison baseline. If your app uses an older major version, then update it before migration or account for the older API differences in each slice.

***

## Step 1: Add Polaris web components

Apps created with the current Shopify CLI template already load Polaris. Check the application's HTML before adding another script. The page must load exactly one Polaris build before the application bundle. Use `polaris-1.js` to follow the newest stable Polaris 1 release:

## HTML

```html
<head>
  <meta name="shopify-api-key" content="%SHOPIFY_API_KEY%" />
  <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
  <script src="https://cdn.shopify.com/shopifycloud/polaris-1.js"></script>
</head>
```

The direct `EmptyState`, `EmptySearchResult`, and `ProgressBar` migrations use components added in Polaris 1.1. Apps on the stable channel, `polaris-1.js`, already have them. If your app pins `polaris-1.0.js`, move to `polaris-1.js` with `@shopify/polaris-types@^1.1.0`, or pin `polaris-1.1.js` with `@shopify/polaris-types@~1.1.0` so the types stay on 1.1 like the script tag; see [Polaris versioning](https://shopify.dev/docs/api/app-home/v1.1/web-components/versioning). Keep the runtime and type versions aligned.

If your app sets a Content Security Policy, then add `https://cdn.shopify.com` to the `script-src` directive in the same deployment. Test the response header for every controller or route that serves the embedded HTML shell.

Install the type-only packages for the global web components and App Bridge APIs:

## Terminal

```terminal
npm install --save-dev @shopify/polaris-types@latest @shopify/app-bridge-types@latest
```

Include both packages in the TypeScript project. For example, add them to `compilerOptions.types`:

## tsconfig.json

```json
{
  "compilerOptions": {
    "types": ["@shopify/polaris-types", "@shopify/app-bridge-types"]
  }
}
```

Run the app in an embedded development store and render a small component such as `<s-badge>`. If it appears as unstyled text, then check the browser console and network panel for a blocked or duplicate script before changing the component code.

***

## Step 2: Choose a migration slice

Migrate one self-contained page, route family, or visually neutral shared component at a time. Keep each slice deployable while both component systems coexist.

Within a page, use this order:

1. Migrate the page shell, including `Page`, `Card`, `Layout`, and page actions.
2. Migrate display components such as `Badge`, `Banner`, `Text`, `Icon`, `Image`, and stacks.
3. Migrate buttons, links, menus, modals, and popovers.
4. Migrate form components and reconnect their values, validation, and submission behavior.
5. Migrate tables, filters, selection, and bulk actions as one data-view slice.

Check every call site before migrating a shared component. A web-component leaf can render inside a Polaris React page during migration, but changing layout chrome independently can produce inconsistent spacing and surfaces.

***

## Step 3: Migrate the page shell

Start with `Page`, because it defines the layout and connects page metadata and actions to the Shopify admin. Replace card and layout structures with `s-section`, `s-stack`, `s-grid`, and supported [App Home patterns](https://shopify.dev/docs/api/app-home/patterns).

## Migrating a page and card

##### Polaris web components

```tsx
export function ProductsPage({onCreateProduct}) {
  return (
    <s-page heading="Products">
      <s-button
        slot="primary-action"
        variant="primary"
        onClick={onCreateProduct}
      >
        Create product
      </s-button>
      <s-section heading="All products">
        <s-paragraph>
          Manage the products available through your app.
        </s-paragraph>
      </s-section>
    </s-page>
  );
}
```

##### Polaris React

```tsx
import {Card, Page, Text} from '@shopify/polaris';

export function ProductsPage({onCreateProduct}) {
  return (
    <Page
      title="Products"
      primaryAction={{content: 'Create product', onAction: onCreateProduct}}
    >
      <Card>
        <Text as="h2" variant="headingSm">All products</Text>
        <Text as="p">Manage the products available through your app.</Text>
      </Card>
    </Page>
  );
}
```

In an embedded app, `s-page` coordinates its heading, breadcrumbs, and actions with the admin title bar. A primary page action must be a single `s-button` with `variant="primary"`. Use `slot="breadcrumb-actions"`, `slot="primary-action"`, and `slot="secondary-actions"` for supported title-bar content. `s-page` doesn't have an accessory slot, so omit `titleMetadata` from the page shell. Reintroduce status in page content only when it provides necessary information there. When secondary actions open a menu, slot the trigger button and keep the menu as an unslotted sibling.

There is no `s-card`. Use `s-section` for a standard section, or choose the [app card](https://shopify.dev/docs/api/app-home/patterns/compositions/app-card), [callout card](https://shopify.dev/docs/api/app-home/patterns/compositions/callout-card), or another composition when the old card represented a complete pattern. Review the [`Page`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/page), [`Card`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/card), and [`Layout`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/layout) migration guides for component-specific mappings.

***

## Step 4: Migrate components and interactions

Web components use native element content, attributes, properties, events, and slots instead of React component props that contain other elements.

* Use camelCase for multiword JSX properties, such as `accessibilityLabel`, `commandFor`, `gridTemplateColumns`, and `hasNextPage`.
* Keep slot values kebab-case, such as `slot="primary-action"` and `slot="breadcrumb-actions"`.
* Replace `url` with `href` on links and buttons. Preserve the browser's modified-click behavior when integrating with a client-side router.
* Use `commandFor` and `command` to connect buttons to modals, popovers, menus, and other invoker targets.
* Read form values from `event.currentTarget.value`. Read checkbox and switch state from `event.currentTarget.checked`.
* Replace imported React icons with the documented string name on `s-icon` or an `icon` property. Check the [icon reference](https://shopify.dev/docs/api/app-home/web-components/media-and-visuals/icon) instead of guessing names.

Modal and popover invokers don't require controlled `open` state. Use commands for declarative controls, and use `showOverlay()`, `hideOverlay()`, or `toggleOverlay()` when you need to control an `s-modal` through a ref.

### Polaris React component mapping

Use this index to determine the destination for Polaris React UI components that have an actionable App Home migration. Low-level framework utilities and test-only exports are intentionally omitted. Use the linked pattern or focused migration guide and review the resulting experience.

Open a component migration guide from the first column of the table. Each guide explains whether to use a direct component, compose a pattern, keep the existing component temporarily, or replace it with native HTML or app-owned behavior.

| Polaris React component | Polaris web components |
| - | - |
| [`AccountConnection`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/account-connection) | [Account connection pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/account-connection) |
| [`ActionList`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/action-list) | [`s-menu`](https://shopify.dev/docs/api/app-home/web-components/actions/menu), or an `s-stack` for persistent actions |
| [`ActionMenu`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/action-menu) | `s-page` action slots and `s-menu` for grouped or overflow actions |
| [`AppProvider`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/app-provider) | Remove after the final Polaris React consumer. Keep app-owned localization and routing outside the provider. |
| [`Autocomplete`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/autocomplete) | No direct equivalent. Keep it during migration or build an accessible custom combobox. |
| [`Avatar`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/avatar) | [`s-avatar`](https://shopify.dev/docs/api/app-home/web-components/media-and-visuals/avatar) |
| [`Backdrop`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/backdrop) | Use the backdrop managed by `s-modal` or `s-popover`. |
| [`Badge`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/badge) | [`s-badge`](https://shopify.dev/docs/api/app-home/web-components/feedback-and-status-indicators/badge) |
| [`Banner`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/banner) | [`s-banner`](https://shopify.dev/docs/api/app-home/web-components/feedback-and-status-indicators/banner) |
| [`Bleed`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/bleed) | No direct equivalent. Rework the containing layout. |
| [`BlockStack`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/block-stack) | [`s-stack direction="block"`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/stack) |
| [`Box`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/box) | [`s-box`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/box) |
| [`Breadcrumbs`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/breadcrumbs) | `s-link` elements in the `s-page` `breadcrumb-actions` slot |
| [`UnstableBulkActions`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/unstable-bulk-actions) | [Index table](https://shopify.dev/docs/api/app-home/patterns/compositions/index-table) or [resource list](https://shopify.dev/docs/api/app-home/patterns/compositions/resource-list) selection and bulk actions |
| [`Button`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/button) | [`s-button`](https://shopify.dev/docs/api/app-home/web-components/actions/button) |
| [`ButtonGroup`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/button-group) | [`s-button-group`](https://shopify.dev/docs/api/app-home/web-components/actions/button-group) |
| [`CalloutCard`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/callout-card) | [Callout card pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/callout-card) |
| [`Card`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/card) | [`s-section`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/section) or [app card pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/app-card) |
| [`Checkbox`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/checkbox) | [`s-checkbox`](https://shopify.dev/docs/api/app-home/web-components/forms/checkbox) |
| [`ChoiceList`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/choice-list) | [`s-choice-list`](https://shopify.dev/docs/api/app-home/web-components/forms/choice-list) and `s-choice` |
| [`Collapsible`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/collapsible) | Conditional rendering or native `details` and `summary`. There is no direct equivalent. |
| [`ColorPicker`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/color-picker) | [`s-color-picker`](https://shopify.dev/docs/api/app-home/web-components/forms/color-picker) or [`s-color-field`](https://shopify.dev/docs/api/app-home/web-components/forms/color-field) |
| [`Combobox`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/combobox) | No direct equivalent. Keep it during migration or build an accessible custom control. |
| [`ContextualSaveBar`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/contextual-save-bar) | [`data-save-bar`](https://shopify.dev/docs/api/app-home/app-bridge-web-components/save-bar) or [Save Bar API](https://shopify.dev/docs/api/app-home/apis/user-interface-and-interactions/save-bar-api) |
| [`DataTable`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/data-table) | [`s-table`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/table) |
| [`DatePicker`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/date-picker) | [`s-date-picker`](https://shopify.dev/docs/api/app-home/web-components/forms/date-picker) or [`s-date-field`](https://shopify.dev/docs/api/app-home/web-components/forms/date-field) |
| [`Divider`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/divider) | [`s-divider`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/divider) |
| [`DropZone`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/drop-zone) | [`s-drop-zone`](https://shopify.dev/docs/api/app-home/web-components/forms/drop-zone) |
| [`EmptySearchResult`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/empty-search-result) | [`s-empty-state`](https://shopify.dev/docs/api/app-home/v1.1/web-components/feedback-and-status-indicators/empty-state) |
| [`EmptyState`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/empty-state) | [`s-empty-state`](https://shopify.dev/docs/api/app-home/v1.1/web-components/feedback-and-status-indicators/empty-state) |
| [`ExceptionList`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/exception-list) | `s-banner`, lists, paragraphs, and inline text composition |
| [`Filters`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/filters) | `s-table` filters with `s-search-field` and `s-select`, or the [resource index template](https://shopify.dev/docs/api/app-home/patterns/templates/resource-index) |
| [`FooterHelp`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/footer-help) | [Footer help pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/footer-help) |
| [`Form`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/form) | Native `form` element |
| [`FormLayout`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/form-layout) | [`s-grid`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/grid) composition |
| [`Frame`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/frame) | Remove. App Bridge and the Shopify admin provide embedded-app chrome. |
| [`FullscreenBar`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/fullscreen-bar) | [`s-app-window`](https://shopify.dev/docs/api/app-home/app-bridge-web-components/app-window) for a dedicated fullscreen workflow |
| [`Grid`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/grid) | [`s-grid`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/grid) |
| [`Icon`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/icon) | [`s-icon`](https://shopify.dev/docs/api/app-home/web-components/media-and-visuals/icon) |
| [`Image`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/image) | [`s-image`](https://shopify.dev/docs/api/app-home/web-components/media-and-visuals/image) |
| [`IndexFilters`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/index-filters) | [Resource index template](https://shopify.dev/docs/api/app-home/patterns/templates/resource-index) |
| [`IndexTable`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/index-table) | [Index table pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/index-table) |
| [`Indicator`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/indicator) | `s-badge`, `s-icon`, or text that communicates the status |
| [`InlineCode`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/inline-code) | Native `code` element |
| [`InlineError`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/inline-error) | `s-text tone="critical"` or `s-banner` for a page-level error |
| [`InlineGrid`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/inline-grid) | [`s-grid`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/grid) |
| [`InlineStack`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/inline-stack) | [`s-stack direction="inline"`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/stack) |
| [`KeyboardKey`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/keyboard-key) | Native `kbd` element |
| [`Labelled`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/labelled) | A field component's label, details, and error properties |
| [`Layout`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/layout) | `s-page` slots, `s-grid`, and `s-stack` |
| [`Link`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/link) | [`s-link`](https://shopify.dev/docs/api/app-home/web-components/actions/link) |
| [`List`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/list) | [`s-ordered-list`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/ordered-list) or [`s-unordered-list`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/unordered-list) |
| [`Listbox`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/listbox) | `s-select` or `s-choice-list` when their interaction fits. There is no general listbox equivalent. |
| [`Loading`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/loading) | [Loading API](https://shopify.dev/docs/api/app-home/apis/user-interface-and-interactions/loading-api) |
| [`MediaCard`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/media-card) | [Media card pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/media-card) |
| [`Modal`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/modal) | [`s-modal`](https://shopify.dev/docs/api/app-home/web-components/overlays/modal) |
| [`Navigation`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/navigation) | [`s-app-nav`](https://shopify.dev/docs/api/app-home/app-bridge-web-components/app-nav) |
| [`OptionList`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/option-list) | `s-choice-list` or `s-select`, depending on the interaction |
| [`Page`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/page) | [`s-page`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/page) |
| [`PageActions`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/page-actions) | `s-page` action slots or `s-button-group` |
| [`Pagination`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/pagination) | `s-table` pagination, or a button-group composition for non-table content |
| [`Popover`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/popover) | [`s-popover`](https://shopify.dev/docs/api/app-home/web-components/overlays/popover) |
| [`Portal`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/portal) | Keep the framework portal only for custom UI. Polaris overlays don't require it. |
| [`PortalsManager`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/portals-manager) | Remove after migrating Polaris React overlays. |
| [`PositionedOverlay`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/positioned-overlay) | `s-popover`, `s-tooltip`, or `s-modal` |
| [`ProgressBar`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/progress-bar) | `s-progress` |
| [`RadioButton`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/radio-button) | `s-choice-list` and `s-choice` |
| [`RangeSlider`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/range-slider) | Native range input or an accessible custom control. There is no direct equivalent. |
| [`ResourceItem`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/resource-item) | [Resource list pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/resource-list) |
| [`ResourceList`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/resource-list) | [Resource list pattern](https://shopify.dev/docs/api/app-home/patterns/compositions/resource-list) |
| [`ScrollLock`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/scroll-lock) | Remove for Polaris overlays. Use a custom implementation only for custom overlays. |
| [`Scrollable`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/scrollable) | Native scrolling container and app-owned CSS |
| [`Select`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/select) | [`s-select`](https://shopify.dev/docs/api/app-home/web-components/forms/select) |
| [`SelectAllActions`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/select-all-actions) | [Index table](https://shopify.dev/docs/api/app-home/patterns/compositions/index-table) or [resource list](https://shopify.dev/docs/api/app-home/patterns/compositions/resource-list) selection behavior |
| [`SettingToggle`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/setting-toggle) | `s-switch` in the [settings template](https://shopify.dev/docs/api/app-home/patterns/templates/settings) |
| [`Sheet`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/sheet) | `s-modal` for a dialog or `s-app-window` for a full workflow |
| [`SkeletonBodyText`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/skeleton-body-text) | `s-spinner` loading state. No skeleton equivalent is available. |
| [`SkeletonDisplayText`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/skeleton-display-text) | `s-spinner` loading state. No skeleton equivalent is available. |
| [`SkeletonPage`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/skeleton-page) | `s-page` with an `s-spinner`. No skeleton equivalent is available. |
| [`SkeletonTabs`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/skeleton-tabs) | `s-spinner` loading state. No tabs or skeleton equivalent is available. |
| [`SkeletonThumbnail`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/skeleton-thumbnail) | `s-spinner` loading state. No skeleton equivalent is available. |
| [`Spinner`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/spinner) | [`s-spinner`](https://shopify.dev/docs/api/app-home/web-components/feedback-and-status-indicators/spinner) |
| [`Sticky`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/sticky) | App-owned CSS using `position: sticky` |
| [`Tabs`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/tabs) | Route navigation or an accessible custom tab interface. There is no direct equivalent. |
| [`Tag`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/tag) | [`s-chip`](https://shopify.dev/docs/api/app-home/web-components/typography-and-content/chip) or [`s-clickable-chip`](https://shopify.dev/docs/api/app-home/web-components/actions/clickable-chip) |
| [`Text`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/text) | `s-text`, `s-paragraph`, or `s-heading`, based on semantics |
| [`TextContainer`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/text-container) | `s-stack direction="block"` |
| [`TextField`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/text-field) | `s-text-field`, `s-email-field`, `s-number-field`, `s-password-field`, `s-url-field`, `s-search-field`, or `s-text-area` |
| [`ThemeProvider`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/theme-provider) | Remove. Polaris web components receive their appearance from the CDN library. |
| [`Thumbnail`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/thumbnail) | [`s-thumbnail`](https://shopify.dev/docs/api/app-home/web-components/media-and-visuals/thumbnail) |
| [`Toast`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/toast) | [Toast API](https://shopify.dev/docs/api/app-home/apis/user-interface-and-interactions/toast-api) |
| [`Tooltip`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/tooltip) | [`s-tooltip`](https://shopify.dev/docs/api/app-home/web-components/typography-and-content/tooltip) |
| [`TopBar`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/top-bar) | `s-page` title-bar integration and `s-app-nav` |
| [`TrapFocus`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/trap-focus) | Remove for Polaris overlays. Keep an accessible focus trap only for custom dialogs. |
| [`Truncate`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/truncate) | App-owned text-overflow CSS, with full content available accessibly |
| [`UnstyledButton`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/unstyled-button) | Native `button` or [`s-clickable`](https://shopify.dev/docs/api/app-home/web-components/actions/clickable) |
| [`UnstyledLink`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/unstyled-link) | Native `a` or `s-clickable` with `href` |
| [`VideoThumbnail`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/video-thumbnail) | `s-thumbnail` or `s-image` with a custom play affordance |

Each linked component has its own migration guide. Migrate compound features by opening every component guide involved and moving the feature as one working slice. For example, migrate `IndexFilters`, `IndexTable`, selection, and bulk actions together even though each responsibility has a separate guide.

***

## Step 5: Replace Polaris React integrations

Polaris React's `Frame` tree provided app chrome and hosted components such as `Toast`, `Loading`, and `ContextualSaveBar`. In an embedded App Home app, App Bridge and the Shopify admin own those integrations.

Use these replacements:

| Polaris React integration | Polaris web components |
| - | - |
| `ContextualSaveBar` | Add `data-save-bar` to a native form, or use `shopify.saveBar`. |
| `Frame` and `TopBar` | Remove them. Use `s-page` for page context and `s-app-nav` for app navigation. |
| `Loading` | Call `shopify.loading(true)` and `shopify.loading(false)`. |
| `Navigation` | Use `s-app-nav`. |
| `Toast` | Call `shopify.toast.show(message, options)`. |

Use a native `form` element instead of looking for an `s-form` in App Home. The automatic save bar observes native forms through `data-save-bar`.

Review the [`AppProvider`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/app-provider), [`Frame`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/frame), [`TopBar`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/top-bar), and [`Navigation`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/navigation) migration guides before removing the old app shell.

***

## Step 6: Update tests

Unit-test environments such as jsdom don't load the CDN script, so `s-*` elements remain un-upgraded custom-element hosts without shadow DOM. Test the markup and your application behavior, not Polaris's internal rendering.

* Query the rendered custom-element tag and inspect its attributes or properties.
* Don't query roles or content that only exists in the component's shadow DOM.
* Dispatch real DOM events for fields and component-specific events. Calling a React prop directly can hide a broken browser binding.
* Mock `window.shopify` methods used by the migrated slice.
* Stub `showOverlay()`, `hideOverlay()`, and `toggleOverlay()` if a unit test controls `s-modal` imperatively.
* Provide complete `ResizeObserver` and `matchMedia` mocks when the test environment needs them.

After unit tests pass, walk the slice in a development store. Confirm title-bar actions, keyboard navigation, forms, overlays, routing, responsive layouts, and browser console warnings.

***

## Step 7: Remove Polaris React

After the last embedded slice and all dependent packages are migrated:

1. Remove imports from `@shopify/polaris` and `@shopify/polaris-icons`.
2. Remove `AppProvider`, `Frame`, `PolarisTestProvider`, and Polaris React stylesheets.
3. Remove packages that exist only to support Polaris React.
4. Remove obsolete `.Polaris-*` selectors and token overrides.
5. Remove `@shopify/polaris` from the package manifest and reinstall dependencies.
6. Add an ESLint restricted-import rule for `@shopify/polaris` in the migrated application tree.

If a public, authentication, or standalone route still requires Polaris React, then keep the dependency and scope the import restriction to the embedded routes. Record the remaining boundary so future changes don't reintroduce Polaris React into migrated pages.

***

## Step 8: Verify the migration

Before deploying the final slice:

* Confirm that only one Polaris script loads and that the production Content Security Policy permits it.
* Run the app's formatter, linter, type checker, unit tests, integration tests, and production build.
* Search the migrated tree for `@shopify/polaris`, `@shopify/polaris-icons`, and `.Polaris-`.
* Verify every new event binding with a real browser event.
* Test internal navigation with normal clicks, modified clicks, the back button, and copied URLs.
* Test forms with keyboard submission, validation errors, save and discard behavior, and unsaved-change protection.
* Test translated content and remove translation keys that belonged only to deleted Polaris React UI.
* Test the embedded app at supported viewport sizes and with keyboard and screen-reader navigation.
* Verify the page heading, breadcrumbs, accessory, and actions in the Shopify admin title bar.

***

## Next steps

* Explore [App Home page patterns](https://shopify.dev/docs/api/app-home/patterns) before building a custom composition.
* Use the [Polaris web components reference](https://shopify.dev/docs/api/app-home/web-components) to verify component properties, events, methods, and slots.
* Use [App Bridge APIs](https://shopify.dev/docs/api/app-home/apis) and [App Bridge web components](https://shopify.dev/docs/api/app-home/app-bridge-web-components) for behavior outside the app iframe.
* Review the [app design guidance](https://shopify.dev/docs/apps/design) before changing the experience of a component that has no direct equivalent.

***
