Skip to main content

Migrate ResourceItem to the Polaris section component

The Polaris section component groups related content inside a bordered container with an optional heading and action slots. It replaces the previous ResourceItem component and is available as <s-section> in API versions 2025-10 and newer.

Migrating ResourceItem to s-section

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

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

function Extension() {
return (
<s-section heading="Order #1001">
<s-text>Placed on April 12, 2026</s-text>
<s-button slot="primary-action" variant="primary">
Buy again
</s-button>
<s-button slot="secondary-actions">View details</s-button>
</s-section>
);
}
import {
Button,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem
action={
<>
<Button kind="primary">Buy again</Button>
<Button kind="secondary">View details</Button>
</>
}
>
<Text>Order #1001</Text>
<Text>Placed on April 12, 2026</Text>
</ResourceItem>
);
}

The following properties are different in the Polaris section component.

The previous ResourceItem action prop accepted a fragment of Button components that were rendered alongside the item's content. On the Polaris section, use the primary-action and secondary-actions slots instead. Assign a single button to primary-action and one or more buttons to secondary-actions using the slot attribute.

Migrating action to slots

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

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

function Extension() {
return (
<s-section heading="Order #1001">
<s-text>Placed on April 12, 2026</s-text>
<s-button slot="primary-action" variant="primary">
Buy again
</s-button>
<s-button slot="secondary-actions">Track shipment</s-button>
<s-button slot="secondary-actions">Return</s-button>
</s-section>
);
}
import {
Button,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem
action={
<>
<Button kind="primary">Buy again</Button>
<Button kind="secondary">Track shipment</Button>
<Button kind="secondary">Return</Button>
</>
}
>
<Text>Order #1001</Text>
<Text>Placed on April 12, 2026</Text>
</ResourceItem>
);
}

Anchor to Button changes in action slotsButton changes in action slots

Buttons placed in the primary-action and secondary-actions slots use the updated <s-button> API. The following properties are different from the previous Button used inside action.

The previous Button kind prop is now variant. Use the following mapping:

Previous kindNew variant
'primary''primary'
'secondary''secondary'
'plain'Removed.

The previous Button onPress prop is now called onClick.

The previous Button to prop is now called href.

The previous Button overlay prop has been removed. To open a modal from a button in the primary-action or secondary-actions slot, use command and commandFor to target a sibling modal by ID.

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-section heading="Order #1001">
<s-text>Placed on April 12, 2026</s-text>
<s-button
slot="primary-action"
variant="primary"
command="--show"
commandFor="order-details-modal"
>
View details
</s-button>
</s-section>
<s-modal id="order-details-modal" heading="Order details">
<s-text>Your order ships in 2 to 4 business days.</s-text>
</s-modal>
</>
);
}
import {
Button,
Modal,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem
action={
<Button
kind="primary"
overlay={
<Modal id="order-details-modal" title="Order details">
<Text>Your order ships in 2 to 4 business days.</Text>
</Modal>
}
>
View details
</Button>
}
>
<Text>Order #1001</Text>
<Text>Placed on April 12, 2026</Text>
</ResourceItem>
);
}

The previous Button loadingLabel prop has been removed.


The Polaris section component introduces the following new properties:

New propTypeDescription
headingstringSets the section's visible heading.
idstringA unique identifier for the section.

The previous ResourceItem onPress and to props made the whole item interactive or turned it into a link. The Polaris section is purely structural and doesn't handle interaction directly. Wrap the content you want to make interactive in <s-clickable>: set href to replace to, or read presses from its click event to replace onPress.

Buttons in the primary-action and secondary-actions slots stay interactive on their own and don't need to be wrapped.

Migrating onPress and to to s-clickable

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

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

function Extension() {
return (
<s-section heading="Order #1001">
<s-clickable href="shopify:customer-account/orders/1001">
<s-text>Placed on April 12, 2026</s-text>
</s-clickable>
<s-button slot="primary-action" variant="primary">
Buy again
</s-button>
</s-section>
);
}
import {
Button,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem
to="shopify:customer-account/orders/1001"
action={<Button kind="primary">Buy again</Button>}
>
<Text>Order #1001</Text>
<Text>Shipped</Text>
</ResourceItem>
);
}

Anchor to actionLabel and actionAccessibilityLabelactionLabel and actionAccessibilityLabel

The previous actionLabel and actionAccessibilityLabel props have been removed. Buttons placed in the primary-action and secondary-actions slots carry their own label and accessibilityLabel.

By default, the secondary-actions slot renders extra buttons inline when there's room and collapses the rest into a menu at narrower widths, using its own built-in trigger. To set the label on that trigger (the equivalent of the previous actionLabel), replace the slot's built-in trigger with your own button that targets an <s-menu> with commandFor. Opting in to this pattern turns off the automatic responsive behavior: the actions stay behind the menu at every width instead of rendering inline when there's space.

Customizing the menu trigger label

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

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

function Extension() {
return (
<s-section heading="Order #1001">
<s-text>Placed on April 12, 2026</s-text>
<s-button slot="primary-action" variant="primary">
Buy again
</s-button>
<s-button
slot="secondary-actions"
variant="secondary"
commandFor="order-actions-menu"
>
Manage
</s-button>
<s-menu id="order-actions-menu">
<s-button>Track shipment</s-button>
<s-button>Return</s-button>
</s-menu>
</s-section>
);
}
import {
Button,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem
actionLabel="Manage"
action={
<>
<Button kind="primary">Buy again</Button>
<Button kind="secondary">Track shipment</Button>
<Button kind="secondary">Return</Button>
</>
}
>
<Text>Order #1001</Text>
<Text>Placed on April 12, 2026</Text>
</ResourceItem>
);
}

The previous loading prop rendered a loading state on the whole item. The Polaris section doesn't have a loading state. If you need to indicate loading, set loading on the button inside primary-action, or render a <s-spinner> alongside the content.


Anchor to Matching the previous spacingMatching the previous spacing

The default gap between the heading, content, and action buttons is tighter in <s-section> than it was in ResourceItem. To approximate the previous spacing, wrap the section's content in <s-stack> with direction="block" and paddingBlock="large". Buttons stay in their slots and aren't wrapped.

Approximating the previous spacing

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

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

function Extension() {
return (
<s-section heading="Order #1001">
<s-stack direction="block" paddingBlock="large">
<s-text>Placed on April 12, 2026</s-text>
</s-stack>
<s-button slot="primary-action" variant="primary">
Buy again
</s-button>
<s-button slot="secondary-actions">View details</s-button>
</s-section>
);
}
import {
Button,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem
action={
<>
<Button kind="primary">Buy again</Button>
<Button kind="secondary">View details</Button>
</>
}
>
<Text>Order #1001</Text>
<Text>Placed on April 12, 2026</Text>
</ResourceItem>
);
}

Anchor to Using s-image-group inside s-sectionUsing s-image-group inside s-section

When <s-image-group> is rendered inside an <s-section>, it always uses a grid layout. The variant prop has been removed — the grid layout is implicit inside a section, so drop variant from existing usages.

Two other props have been removed:

  • accessibilityLabel — describe each image with its own alt attribute instead.
  • loading — each <s-image> manages its own loading state automatically.

Migrating image-group inside a section

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

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

function Extension() {
return (
<s-section heading="Wishlist">
<s-image-group totalItems={7}>
<s-image src="https://example.com/product-1.jpg" alt="Blue t-shirt" />
<s-image src="https://example.com/product-2.jpg" alt="Red mug" />
<s-image src="https://example.com/product-3.jpg" alt="Canvas tote" />
<s-image src="https://example.com/product-4.jpg" alt="Notebook" />
</s-image-group>
</s-section>
);
}
import {
Image,
ImageGroup,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem>
<Text>Wishlist</Text>
<ImageGroup variant="grid" totalItems={7}>
<Image source="https://example.com/product-1.jpg" accessibilityDescription="Blue t-shirt" />
<Image source="https://example.com/product-2.jpg" accessibilityDescription="Red mug" />
<Image source="https://example.com/product-3.jpg" accessibilityDescription="Canvas tote" />
<Image source="https://example.com/product-4.jpg" accessibilityDescription="Notebook" />
</ImageGroup>
</ResourceItem>
);
}

Anchor to Recreating the inline-stack image group layoutRecreating the inline-stack image group layout

The grid layout is the preferred way to display multiple images inside a section. If you need the overlapping inline-stack look from the previous ImageGroup, you can recreate it with an <s-grid> that uses overlapping column widths:

Recreating inline-stack with s-grid

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

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

function Extension() {
return (
<s-section heading="Wishlist">
<s-grid gridTemplateColumns="28px 28px 28px 42px" alignItems="center">
<s-box
inlineSize="42px"
blockSize="42px"
border="base"
borderRadius="base"
overflow="hidden"
>
<s-image
src="https://example.com/product-1.jpg"
alt="Blue t-shirt"
aspectRatio="1/1"
objectFit="cover"
/>
</s-box>
<s-box
inlineSize="42px"
blockSize="42px"
border="base"
borderRadius="base"
overflow="hidden"
>
<s-image
src="https://example.com/product-2.jpg"
alt="Red mug"
aspectRatio="1/1"
objectFit="cover"
/>
</s-box>
<s-box
inlineSize="42px"
blockSize="42px"
border="base"
borderRadius="base"
overflow="hidden"
>
<s-image
src="https://example.com/product-3.jpg"
alt="Canvas tote"
aspectRatio="1/1"
objectFit="cover"
/>
</s-box>
<s-stack
direction="inline"
inlineSize="42px"
blockSize="42px"
borderRadius="large-100"
border="large base solid"
background="subdued"
alignItems="center"
justifyContent="center"
>
<s-text>+3</s-text>
</s-stack>
</s-grid>
</s-section>
);
}
import {
Image,
ImageGroup,
ResourceItem,
Text,
reactExtension,
} from '@shopify/ui-extensions-react/customer-account';

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

function Extension() {
return (
<ResourceItem>
<Text>Wishlist</Text>
<ImageGroup variant="inline-stack" totalItems={6}>
<Image source="https://example.com/product-1.jpg" accessibilityDescription="Blue t-shirt" />
<Image source="https://example.com/product-2.jpg" accessibilityDescription="Red mug" />
<Image source="https://example.com/product-3.jpg" accessibilityDescription="Canvas tote" />
</ImageGroup>
</ResourceItem>
);
}

Was this page helpful?