S-badge icon prop is ignored when the badge is in s-page's accessory slot

An s-badge slotted into s-page’s accessory slot renders its tone and text correctly, but the icon prop is silently dropped:

<s-page heading="PO-1001">
  <s-badge slot="accessory" tone="auto" icon="enabled">Ordered</s-badge>
</s-page>

Expected: badge next to the page heading with the “enabled” icon (like the status badges in Shopify admin, e.g. the Ordered badge on native purchase orders).

Actual: badge shows with the right tone and label, but no icon.

The exact same s-badge with icon="enabled" renders the icon fine anywhere in the page body, so the icon value itself is valid - it’s specifically the accessory slot that loses it. Embedded app, App Bridge + polaris.js from the Shopify CDN (latest), React 19.

Related but different: topic 32846 reports the accessory badge not rendering at all - for us the badge renders, only the icon is stripped.

Is the accessory slot re-rendering the badge in the admin title bar with a limited subset of props? Any workaround to get an icon in there?

Hey @Luke - thanks for flagging this, hope all’s well! It does look weird, especially since the same badge works correctly in the page body.

Could you try icon="view" or icon="clock" and confirm whether it also works in the body but disappears from the accessory slot? If possible, could you also check whether the icon attribute remains on the <s-badge> element after React hydrates?

Definitely strange, so happy to dig in for sure.

Thanks Alan, tested both.

icon="view" and icon="clock" behave exactly like our mapped values: they render fine in the page body, and are ignored in the accessory slot. So it isn’t value or type specific.

On hydration, the attribute stays put. I logged each badge at mount and again 1.5s later, and icon is present as both an attribute and a property the whole time:

<s-badge id="badge-accessory" slot="accessory" tone="auto" icon="enabled">Ordered</s-badge>

The interesting part is what happens after that. For badges in the accessory slot, assignedSlot is null at both checks, so the element is never actually projected into s-page’s shadow DOM. Then roughly a second after mount the source element picks up display: none:

<s-badge id="badge-accessory" slot="accessory" tone="auto" icon="enabled" style="display: none;">Ordered</s-badge>

Badges rendered in the page body have assignedSlot === "" as expected and keep their icon.

So it looks like s-page copies or rebuilds accessory content into its header rather than slotting it, and icon doesn’t survive that copy while tone does. That would explain why the identical element works in the body.

One extra data point: I put three badges in slot="accessory" and only the first rendered in the header. The other two disappeared from the header entirely, though they were still present in the light DOM.

React 19, App Bridge from the CDN.

Hey @Luke - thanks for digging into that. I checked the current App Bridge path, and your read is right from what I can tell: the title-bar handoff takes the first accessory badge and forwards its text and tone, but not its icon, then hides the source element. So I don’t think this is React hydration or an invalid icon value issue :thinking: .

I couldn’t find the icon limitation documented on our end, and the current component validator accepts the markup, so I’m going to raise this internally.

For now, there isn’t a supported way to preserve the icon there. The practical workaround is text and tone in the title bar, or an icon-bearing badge in the page body, but I’ll loop back when I have an update on this from our side - thanks for flagging!