Skip to main content

Migrate Page to the Polaris page component

The Polaris page component is the outer wrapper of a full page, including the page heading, subheading, and page-level actions. It replaces the previous Page component and is available as <s-page> in API versions 2025-10 and newer.

Migrating Page to s-page

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-page heading="Order #1411" subheading="Confirmed Oct 5">
<s-button slot="primary-action" onClick={() => {}}>
Buy again
</s-button>
<s-text>Content</s-text>
</s-page>
);
}
import {
Button,
Page,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

export default reactExtension(
'customer-account.page.render',
() => <Extension />,
);

function Extension() {
return (
<Page
title="Order #1411"
subtitle="Confirmed Oct 5"
primaryAction={<Button onPress={() => {}}>Buy again</Button>}
>
Content
</Page>
);
}

The previous Page title prop has been renamed to heading. It's now optional.

The previous Page subtitle prop has been renamed to subheading.

The previous Page primaryAction prop accepted a RemoteFragment containing one or more Button components, optionally grouped behind an overflow label. The Polaris page component splits this into two slots:

  • Place the first action in the primary-action slot. This slot accepts a single button and renders it as the prominent primary action.
  • Place any additional actions in the secondary-actions slot.
Previous propNew slot
primaryAction={<Button>…</Button>} (single button)<s-button slot="primary-action">…</s-button>
primaryAction={<><Button>A</Button><Button>B</Button></>} (multiple buttons)First button in the primary-action slot, remaining buttons in the secondary-actions slot

Buttons in the primary-action and secondary-actions slots share the same supported properties. See Primary and secondary slot button properties.

Migrating multiple primary actions to slots

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-page heading="Order #1411">
<s-button slot="primary-action" onClick={() => {}}>
Buy again
</s-button>
<s-button slot="secondary-actions" onClick={() => {}}>
Track package
</s-button>
<s-button slot="secondary-actions" onClick={() => {}}>
Return items
</s-button>
<s-text>Order details</s-text>
</s-page>
);
}
import {
Button,
Page,
TextBlock,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

export default reactExtension(
'customer-account.page.render',
() => <Extension />,
);

function Extension() {
return (
<Page
title="Order #1411"
primaryAction={
<>
<Button onPress={() => {}}>Buy again</Button>
<Button onPress={() => {}}>Track package</Button>
<Button onPress={() => {}}>Return items</Button>
</>
}
>
<TextBlock>Order details</TextBlock>
</Page>
);
}

The previous Page secondaryAction prop rendered as a back-arrow breadcrumb link in the page header. It's been renamed to the breadcrumb-actions slot. Place an <s-button> in the slot to replace the previous prop.

Previous propNew slot
secondaryAction={<Button>Label</Button>}<s-button slot="breadcrumb-actions" accessibilityLabel="Label" />

Buttons in the breadcrumb-actions slot only support a restricted set of properties, and any children are discarded. Use accessibilityLabel to describe the breadcrumb's destination. See Breadcrumb slot button properties for the full restricted set.

Migrating secondaryAction to breadcrumb-actions

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-page heading="Order #1411">
<s-button
slot="breadcrumb-actions"
accessibilityLabel="Back to orders"
href="shopify:customer-account/orders"
/>
<s-text>Order details</s-text>
</s-page>
);
}
import {
Button,
Page,
TextBlock,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

export default reactExtension(
'customer-account.page.render',
() => <Extension />,
);

function Extension() {
return (
<Page
title="Order #1411"
secondaryAction={
<Button to="shopify:customer-account/orders">
Orders
</Button>
}
>
<TextBlock>Order details</TextBlock>
</Page>
);
}

Anchor to primaryActionLabel and primaryActionAccessibilityLabelprimaryActionLabel and primaryActionAccessibilityLabel

The previous primaryActionLabel and primaryActionAccessibilityLabel props set a custom label on the overflow trigger that grouped multiple primary action buttons together. With the new slot split (see primaryAction), actions render directly into the primary-action and secondary-actions slots and the overflow trigger is managed automatically.

When the secondary-actions slot contains multiple buttons, the extras collapse into a menu with a built-in trigger. The <s-page> web component doesn't expose a prop to customize that trigger's label. If you need control over the label (the equivalent of the previous primaryActionLabel), replace the slot's built-in trigger with your own button that targets an <s-menu> with commandFor.

Customizing the secondary actions menu trigger label

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-page heading="Order #1411">
<s-button slot="primary-action" onClick={() => {}}>
Buy again
</s-button>
<s-button
slot="secondary-actions"
commandFor="order-actions-menu"
>
Manage
</s-button>
<s-menu id="order-actions-menu">
<s-button>Track package</s-button>
<s-button>Return items</s-button>
</s-menu>
<s-text>Order details</s-text>
</s-page>
);
}
import {
Button,
Page,
TextBlock,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

export default reactExtension(
'customer-account.page.render',
() => <Extension />,
);

function Extension() {
return (
<Page
title="Order #1411"
primaryActionLabel="Manage"
primaryAction={
<>
<Button onPress={() => {}}>Buy again</Button>
<Button onPress={() => {}}>Track package</Button>
<Button onPress={() => {}}>Return items</Button>
</>
}
>
<TextBlock>Order details</TextBlock>
</Page>
);
}

The previous loading prop rendered a loading state on the page-owned UI elements, such as the action buttons in the header. The Polaris page doesn't have a page-level loading state. If you need to indicate loading, set loading on the button inside primary-action, or render an <s-spinner> alongside the content.


Anchor to Primary and secondary slot button propertiesPrimary and secondary slot button properties

Buttons placed in the primary-action and secondary-actions slots only support a subset of the <s-button> properties.

Anchor to Removed slot button propertiesRemoved slot button properties

The following props from the previous Button component are no longer supported inside action slots:

Removed propMigration notes
loadingLabelRemoved. Use the default loading indicator by setting loading.
overlayRemoved. Use command and commandFor to target a sibling component by ID.

To open a modal when the slot button is activated, target a sibling <s-modal> by ID. To close the modal, use command="--hide" with commandFor, or call the modal's hideOverlay() method.

Migrating overlay to command and commandFor

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-page heading="Order #1411">
<s-button
slot="primary-action"
command="--show"
commandFor="cancel-order"
>
Cancel order
</s-button>
<s-modal id="cancel-order" heading="Cancel order">
<s-text>Are you sure? This action can't be undone.</s-text>
<s-button
slot="primary-action"
command="--hide"
commandFor="cancel-order"
>
Confirm cancellation
</s-button>
</s-modal>
<s-text>Order details</s-text>
</s-page>
);
}
import {
Button,
Modal,
Page,
TextBlock,
reactExtension,
useApi,
} from '@shopify/ui-extensions-react/customer-account';

export default reactExtension(
'customer-account.page.render',
() => <Extension />,
);

function Extension() {
const {ui} = useApi();

return (
<Page
title="Order #1411"
primaryAction={
<Button
overlay={
<Modal
id="cancel-order"
title="Cancel order"
primaryAction={
<Button onPress={() => ui.overlay.close('cancel-order')}>
Confirm cancellation
</Button>
}
>
<TextBlock>
Are you sure? This action can't be undone.
</TextBlock>
</Modal>
}
>
Cancel order
</Button>
}
>
<TextBlock>Order details</TextBlock>
</Page>
);
}

Anchor to Updated slot button propertiesUpdated slot button properties

The previous Button onPress prop is now called onClick.

The previous Button to prop is now called href.

Anchor to New slot button propertiesNew slot button properties

Slot buttons support the following new properties:

New propDescription
commandSets the action to run on the target component when the button is activated.
commandForSets the ID of the target component for the command.

Buttons placed in the breadcrumb-actions slot render as a back-arrow breadcrumb link in the page header and only support a restricted set of <s-button> properties. Children aren't supported — describe the destination with accessibilityLabel instead.

Anchor to Supported propertiesSupported properties

PropDescription
accessibilityLabelRequired. A label that describes the breadcrumb's destination to assistive technologies. The button's text content is discarded, so this label is what screen readers announce.
onClickA callback that fires when the breadcrumb is activated. Replaces the previous Button onPress prop.
hrefThe URL to navigate to when the breadcrumb is activated. Replaces the previous Button to prop.

All other Button props from the previous API are unsupported in the breadcrumb-actions slot.


Was this page helpful?