Conditional Polaris component (s-text-field) rendered in wrong position after s-select change

Hi,

I’m working on an Admin UI Extension using Polaris Web Components (API version 2026-01) and I’ve encountered unexpected behavior when conditionally rendering a field based on the selected option in s-select.

I want to show an s-text-field only when a specific option is selected in the dropdown.

Simplified example:

const [type, setType] = useState("simple");

<s-admin-action heading="Example action">
  <s-select
    label="Type"
    value={type}
    onChange={(e) => setType(e.currentTarget.value)}
  >
    <s-option value="simple">Simple</s-option>
    <s-option value="custom">Custom</s-option>
  </s-select>

  {type === "custom" && (
    <s-text-field label="Custom value" />
  )}

  <s-text-field label="Another field" />
</s-admin-action>

Expected behavior

When selecting custom, the field should appear between the select and the second text field:

select
text-field (custom value)
text-field (another field)

Actual behavior

The field appears, but it is always appended at the end of the container:

select
text-field (another field)
text-field (custom value)

It looks as if the component is mounted at the end of the container by the UI extensions runtime instead of in the position defined in JSX.

I also noticed an additional behavior:

  • if the app initially loads with the state type = “custom”, the field is rendered in the correct position

  • however, if I:

    1. change the value in s-select to something else (for example simple)

    2. and then select custom again

the field moves to the end of the container.

So the issue seems to appear only after the state changes and the component is rendered again.

@sebastian.pisula this is a known quirk with Polaris web components and conditional rendering. When a component is unmounted and remounted after a state change, the web component runtime appends it at the end of the DOM rather than respecting the JSX position — it’s a reconciliation issue between Preact’s virtual DOM and the actual web component lifecycle.

The fix is to keep the component mounted but toggle visibility instead of conditionally rendering it:

<s-admin-action heading="Example action">
  <s-select
    label="Type"
    value={type}
    onChange={(e) => setType(e.currentTarget.value)}
  >
    <s-option value="simple">Simple</s-option>
    <s-option value="custom">Custom</s-option>
  </s-select>

  <s-text-field
    label="Custom value"
    style={{ display: type === "custom" ? "block" : "none" }}
  />

  <s-text-field label="Another field" />
</s-admin-action>

This way the DOM order never changes, only visibility does. Keeps the position correct regardless of how many times the select value changes.