Skip to main content

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.


Use the Shopify 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

    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.


  • Use an iframe-based App Home app.
  • 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 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.


Anchor to Step 1: Add Polaris web componentsStep 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

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

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

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


Anchor to Step 2: Choose a migration sliceStep 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.


Anchor to Step 3: Migrate the page shellStep 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.

Migrating a page and card

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>
);
}
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>
);
}

Preview

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, callout card, or another composition when the old card represented a complete pattern. Review the Page, Card, and Layout migration guides for component-specific mappings.


Anchor to Step 4: Migrate components and interactionsStep 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 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.

Anchor to Polaris React component mappingPolaris 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 componentPolaris web components
AccountConnectionAccount connection pattern
ActionLists-menu, or an s-stack for persistent actions
ActionMenus-page action slots and s-menu for grouped or overflow actions
AppProviderRemove after the final Polaris React consumer. Keep app-owned localization and routing outside the provider.
AutocompleteNo direct equivalent. Keep it during migration or build an accessible custom combobox.
Avatars-avatar
BackdropUse the backdrop managed by s-modal or s-popover.
Badges-badge
Banners-banner
BleedNo direct equivalent. Rework the containing layout.
BlockStacks-stack direction="block"
Boxs-box
Breadcrumbss-link elements in the s-page breadcrumb-actions slot
UnstableBulkActionsIndex table or resource list selection and bulk actions
Buttons-button
ButtonGroups-button-group
CalloutCardCallout card pattern
Cards-section or app card pattern
Checkboxs-checkbox
ChoiceLists-choice-list and s-choice
CollapsibleConditional rendering or native details and summary. There is no direct equivalent.
ColorPickers-color-picker or s-color-field
ComboboxNo direct equivalent. Keep it during migration or build an accessible custom control.
ContextualSaveBardata-save-bar or Save Bar API
DataTables-table
DatePickers-date-picker or s-date-field
Dividers-divider
DropZones-drop-zone
EmptySearchResults-empty-state
EmptyStates-empty-state
ExceptionLists-banner, lists, paragraphs, and inline text composition
Filterss-table filters with s-search-field and s-select, or the resource index template
FooterHelpFooter help pattern
FormNative form element
FormLayouts-grid composition
FrameRemove. App Bridge and the Shopify admin provide embedded-app chrome.
FullscreenBars-app-window for a dedicated fullscreen workflow
Grids-grid
Icons-icon
Images-image
IndexFiltersResource index template
IndexTableIndex table pattern
Indicators-badge, s-icon, or text that communicates the status
InlineCodeNative code element
InlineErrors-text tone="critical" or s-banner for a page-level error
InlineGrids-grid
InlineStacks-stack direction="inline"
KeyboardKeyNative kbd element
LabelledA field component's label, details, and error properties
Layouts-page slots, s-grid, and s-stack
Links-link
Lists-ordered-list or s-unordered-list
Listboxs-select or s-choice-list when their interaction fits. There is no general listbox equivalent.
LoadingLoading API
MediaCardMedia card pattern
Modals-modal
Navigations-app-nav
OptionLists-choice-list or s-select, depending on the interaction
Pages-page
PageActionss-page action slots or s-button-group
Paginations-table pagination, or a button-group composition for non-table content
Popovers-popover
PortalKeep the framework portal only for custom UI. Polaris overlays don't require it.
PortalsManagerRemove after migrating Polaris React overlays.
PositionedOverlays-popover, s-tooltip, or s-modal
ProgressBars-progress
RadioButtons-choice-list and s-choice
RangeSliderNative range input or an accessible custom control. There is no direct equivalent.
ResourceItemResource list pattern
ResourceListResource list pattern
ScrollLockRemove for Polaris overlays. Use a custom implementation only for custom overlays.
ScrollableNative scrolling container and app-owned CSS
Selects-select
SelectAllActionsIndex table or resource list selection behavior
SettingToggles-switch in the settings template
Sheets-modal for a dialog or s-app-window for a full workflow
SkeletonBodyTexts-spinner loading state. No skeleton equivalent is available.
SkeletonDisplayTexts-spinner loading state. No skeleton equivalent is available.
SkeletonPages-page with an s-spinner. No skeleton equivalent is available.
SkeletonTabss-spinner loading state. No tabs or skeleton equivalent is available.
SkeletonThumbnails-spinner loading state. No skeleton equivalent is available.
Spinners-spinner
StickyApp-owned CSS using position: sticky
TabsRoute navigation or an accessible custom tab interface. There is no direct equivalent.
Tags-chip or s-clickable-chip
Texts-text, s-paragraph, or s-heading, based on semantics
TextContainers-stack direction="block"
TextFields-text-field, s-email-field, s-number-field, s-password-field, s-url-field, s-search-field, or s-text-area
ThemeProviderRemove. Polaris web components receive their appearance from the CDN library.
Thumbnails-thumbnail
ToastToast API
Tooltips-tooltip
TopBars-page title-bar integration and s-app-nav
TrapFocusRemove for Polaris overlays. Keep an accessible focus trap only for custom dialogs.
TruncateApp-owned text-overflow CSS, with full content available accessibly
UnstyledButtonNative button or s-clickable
UnstyledLinkNative a or s-clickable with href
VideoThumbnails-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.


Anchor to Step 5: Replace Polaris React integrationsStep 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 integrationPolaris web components
ContextualSaveBarAdd data-save-bar to a native form, or use shopify.saveBar.
Frame and TopBarRemove them. Use s-page for page context and s-app-nav for app navigation.
LoadingCall shopify.loading(true) and shopify.loading(false).
NavigationUse s-app-nav.
ToastCall 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, Frame, TopBar, and Navigation migration guides before removing the old app shell.


Anchor to Step 6: Update testsStep 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.


Anchor to Step 7: Remove Polaris ReactStep 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.


Anchor to Step 8: Verify the migrationStep 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.


Was this page helpful?