« Provide a s-table-header with a listSlot of 'primary' » on every React-mounted table

s-table-header-row warns « Provide a s-table-header with a listSlot of ‘primary’ » on every React-mounted table, even though the primary header is there

I’m using polaris web components from https://cdn.shopify.com/shopifycloud/polaris.js (CDN build of 2026-09-21), React 18.3.1, Chrome 153, embedded app in the Shopify admin. Same result on a standalone page outside the admin.

What we see. Every s-table our React app mounts logs this once in the console:

polaris: <s-table-header-row> - Provide a s-table-header with a listSlot of 'primary'

Each of those tables has exactly one s-table-header with listSlot="primary", one secondary, and the rest inline or labeled. The table renders correctly right after the warning. Only the console is wrong, but it fires on 16 tables across our admin, so it drowns out real warnings.

Steps to reproduce. One page, no build step. The vanilla-DOM table is silent, the React table warns.

<script src="https://cdn.shopify.com/shopifycloud/polaris.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.js"></script>
<div id="vanilla"></div>
<div id="react"></div>
<script>
// Vanilla: createElement + setAttribute, appended bottom-up, then inserted. No warning.
const mk = (tag, attrs, kids) => {
const el = document.createElement(tag);
for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
for (const k of kids) el.append(k);
return el;
};
document.getElementById('vanilla').append(
mk('s-table', {}, [
mk('s-table-header-row', {}, [
mk('s-table-header', { listSlot: 'primary' }, ['Name']),
mk('s-table-header', { listSlot: 'secondary' }, ['Email']),
mk('s-table-header', { listSlot: 'inline' }, ['Status']),
]),
mk('s-table-body', {}, [mk('s-table-row', {}, [mk('s-table-cell', {}, ['A']), mk('s-table-cell', {}, ['a@x']), mk('s-table-cell', {}, ['ok'])])]),
]),
);

// React 18: same tree, same attributes. Warns once.
const h = React.createElement;
ReactDOM.createRoot(document.getElementById('react')).render(
h('s-table', null,
h('s-table-header-row', null,
h('s-table-header', { listSlot: 'primary' }, 'Name'),
h('s-table-header', { listSlot: 'secondary' }, 'Email'),
h('s-table-header', { listSlot: 'inline' }, 'Status')),
h('s-table-body', null,
h('s-table-row', null, h('s-table-cell', null, 'A'), h('s-table-cell', null, 'a@x'), h('s-table-cell', null, 'ok')))),
);
</script>

Why I think it happens (traced in the bundle). The Polaris base element class overrides setAttribute. While an element is not yet connected, a setAttribute call whose name is a key of the element’s React props (__reactProps$…) is dropped, and Polaris re-applies those props itself in the element’s connectedCallback. That is fine on its own, but connectedCallback runs parent-first. So when React inserts a finished table:

  1. s-table-header-row connects, renders, and reads listSlot on each child. The children still return the labeled default because their attribute write was dropped.
  2. The row finds no primary and warns.
  3. Each s-table-header connects, gets its props applied, dispatches the attribute-change event, and the row re-renders correctly.

With the listslot attribute spelled lowercase it is the same. The vanilla build has no React props on the element, so nothing is swallowed and the row reads the right value the first time.

What we expect. A table whose header row contains one listSlot="primary" header should not warn, whatever framework inserted it. Two ways this could be fixed on the Polaris side:

  • Apply a disconnected element’s React props when they are first seen, before the parent’s connectedCallback reads the children, or
  • Have s-table-header-row defer its listSlot validation until its slotted headers have connected, since the same code already re-renders on their attribute-change event.

We would rather not carry a framework-specific workaround (mounting the headers one commit after the row does silence it) for what the docs present as plain markup.

Is this a known issue, and is a fix planned?

Can you try this out now in the polaris-1.1 release candidate version and see if it fixes the issue? https://cdn.shopify.com/shopifycloud/polaris-1.1-rc.js

I’ve been using 1.1-rc for quite some time, and the issue is present in there too.

polaris:  
<s-table-header-row>
 - Provide a s-table-header with a listSlot of 'primary' polaris-1.1-rc.js:2:54686

I don’t see the warning anymore (on react-router template), looks like it’s fixed?

But it still happens with the html snippet provided above on 1.1-rc.