Skip to main content

Migrate to the Polaris modal component

The Polaris modal component displays content in an overlay dialog that requires customer interaction. It replaces the previous Modal component and is available as <s-modal> in API versions 2025-10 and newer.


The following properties are different in the Polaris modal component.

The previous Modal title prop is now called heading.

The padding prop changed from a boolean to an enum.

Previous valueNew valueMigration notes
true'base''base' is the default.
false'none'Use padding="none" to remove padding.

Migrating padding values

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

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

function Extension() {
return (
<>
<s-button command="--show" commandFor="settings-modal" variant="primary">
Open settings
</s-button>
<s-modal id="settings-modal" heading="Settings" padding="none">
<s-paragraph>Content of the modal</s-paragraph>
</s-modal>
</>
);
}
import {
reactExtension,
Button,
Modal,
TextBlock,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

function Extension() {
return (
<Button
overlay={
<Modal id="settings-modal" title="Settings" padding={false}>
<TextBlock>Content of the modal</TextBlock>
</Modal>
}
>
Open settings
</Button>
);
}

The size prop values have changed.

Previous valueNew valueMigration notes
'small''small' or 'small-100''small' is an alias for 'small-100'.
'auto''base''base' is the default.
'large''large' or 'large-100''large' is an alias for 'large-100'.
'max''max'No change needed.

For more on the scale system, see Scale.

Anchor to primaryAction and secondaryActionsprimaryAction and secondaryActions

The previous Modal primaryAction and secondaryActions props have been replaced with the primary-action and secondary-actions slots.

Previous propNew patternMigration notes
primaryActionslot="primary-action"Place button children with slot="primary-action".
secondaryActionsslot="secondary-actions"Place button children with slot="secondary-actions".

Migrating action props to slots

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

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

function Extension() {
return (
<>
<s-button command="--show" commandFor="confirm-modal">
Delete item
</s-button>
<s-modal id="confirm-modal" heading="Confirm deletion">
<s-paragraph>Are you sure you want to delete this item?</s-paragraph>
<s-button slot="primary-action" command="--hide" commandFor="confirm-modal">
Confirm
</s-button>
<s-button slot="secondary-actions" command="--hide" commandFor="confirm-modal">
Cancel
</s-button>
</s-modal>
</>
);
}
import {
reactExtension,
Button,
Modal,
TextBlock,
useApi,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

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

return (
<Button
overlay={
<Modal
id="confirm-modal"
title="Confirm deletion"
primaryAction={
<Button onPress={() => ui.overlay.close('confirm-modal')}>
Confirm
</Button>
}
secondaryActions={
<Button onPress={() => ui.overlay.close('confirm-modal')}>
Cancel
</Button>
}
>
<TextBlock>Are you sure you want to delete this item?</TextBlock>
</Modal>
}
>
Delete item
</Button>
);
}

The previous onClose prop is now called onHide. The previous onOpen prop is now called onShow.

Previous propNew prop
onCloseonHide
onOpenonShow

Migrating onClose and onOpen

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

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

function Extension() {
return (
<>
<s-button command="--show" commandFor="welcome-modal">
Open welcome
</s-button>
<s-modal
id="welcome-modal"
heading="Welcome"
onShow={() => console.log('Modal opened')}
onHide={() => console.log('Modal closed')}
>
<s-paragraph>Welcome to our store!</s-paragraph>
</s-modal>
</>
);
}
import {
reactExtension,
Modal,
TextBlock,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

function Extension() {
return (
<Modal
id="welcome-modal"
title="Welcome"
onOpen={() => console.log('Modal opened')}
onClose={() => console.log('Modal closed')}
>
<TextBlock>Welcome to our store!</TextBlock>
</Modal>
);
}

The previous pattern of using the overlay prop on Pressable or Button to render a modal inline is no longer supported. Instead, render the modal as a sibling element and use command with commandFor to open it.

Tip

The command prop defaults to --auto, which resolves to --toggle for modals. You can omit command when targeting a modal and only set commandFor.

Migrating overlay to command

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

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

function Extension() {
return (
<>
<s-button command="--show" commandFor="details-modal">
More info
</s-button>
<s-modal id="details-modal" heading="Details">
<s-paragraph>Delivery in 2 to 4 business days.</s-paragraph>
</s-modal>
</>
);
}
import {
reactExtension,
Button,
Modal,
TextBlock,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

function Extension() {
return (
<Button
overlay={
<Modal id="details-modal" title="Details">
<TextBlock>Delivery in 2 to 4 business days.</TextBlock>
</Modal>
}
>
More info
</Button>
);
}

The Polaris modal component introduces the following new properties:

New propTypeDescription
onAfterShoweventFires after the modal has fully opened and animations have completed.
onAfterHideeventFires after the modal has fully closed and animations have completed.
size values 'small-100', 'large-100'—New size options on the scale. 'small' and 'large' remain available as aliases.

The Polaris modal component introduces a hideOverlay() method for programmatically closing the modal. This replaces the previous pattern of using useApi() and ui.overlay.close() to close overlays.

Using hideOverlay to close the modal

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

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

function Extension() {
const modalRef = useRef(null);

function handleConfirm() {
// Process action, then close the modal
modalRef.current?.hideOverlay();
}

return (
<>
<s-button command="--show" commandFor="action-modal">
Take action
</s-button>
<s-modal id="action-modal" heading="Confirm action" ref={modalRef}>
<s-paragraph>Are you sure?</s-paragraph>
<s-button slot="primary-action" onClick={handleConfirm}>
Confirm
</s-button>
</s-modal>
</>
);
}
import {
reactExtension,
Button,
Modal,
TextBlock,
useApi,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

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

function handleConfirm() {
// Process action, then close the modal
ui.overlay.close('action-modal');
}

return (
<Modal
id="action-modal"
title="Confirm action"
primaryAction={
<Button onPress={handleConfirm}>Confirm</Button>
}
>
<TextBlock>Are you sure?</TextBlock>
</Modal>
);
}

Was this page helpful?