S-form save bar disappears when removing inputs from DOM

s-form save bar disappears when removing table rows – dirty state lost on DOM unmount (app-home UI extension)

Goal

I’m building a tabular JSON editor for a metaobject field – the user can add/remove rows and columns, edit cell values, and save the result as JSON back to the metafield. I need the <s-form> save bar to remain visible after a user removes a row or column from the table so they can save the deletion.

Environment

  • App-home UI extension (non-iframe, Preact-based, s-* web components)
  • Shopify CLI 3.x, API version 2026-04
  • Components: s-page, s-form, s-table, s-text-field, s-button

What happens

  1. Page loads a metaobject’s JSON metafield and renders it as a table. Each cell is an <s-text-field> inside an <s-form onSubmit={...} onReset={...}>.
  2. User edits a cell value – save bar appears correctly.
  3. User clicks “Remove row” – the row is removed from state, Preact unmounts the corresponding <s-table-row> and its child <s-text-field> elements.
  4. Result: Save bar disappears. The form no longer considers itself dirty because the tracked input nodes are gone from the DOM.
  5. Expected: Save bar stays visible – data has changed (a row was deleted) and needs saving.

What I’ve tried

  1. Keeping a permanent <s-text-field> and toggling its value prop – this does trigger the save bar, but <s-text-field> has no hidden attribute/property, so it’s always visible to the user.
  2. Programmatic dispatchEvent (input/change) on <s-text-field> – did not trigger the save bar.
  3. Native <input type="hidden"> inside the <s-form> – not rendered by the extension runtime (only s-* components are rendered).
  4. Soft-delete pattern (mark rows as deleted, hide with CSS)style attributes and CSS do not appear to work on s-* web components in the extension sandbox. The elements remain visible.

What I’m asking

Is there a way to either:

  • Visually hide an s-* component while keeping it mounted in the DOM (so <s-form> retains dirty tracking)?
  • Programmatically mark an <s-form> as dirty without a visible input?

I’m specifically looking for a solution within the app-home UI extension (non-iframe). I’m aware of the shopify.saveBar API but that requires the iframe-based extension surface.

Minimal reproduction

const removeRow = (index: number) => {
    // This causes Preact to unmount the <s-text-field> nodes
    // and <s-form> loses dirty state
    setRows((prev) => prev.filter((_, i) => i !== index));
};

// Inside <s-form>:
{rows.map((row, rowIndex) => (
    <s-table-row key={rowIndex}>
        <s-table-cell>
            <s-text-field
                name={`row-${rowIndex}-col`}
                value={row.value}
                onInput={...}
            />
        </s-table-cell>
        <s-table-cell>
            <s-button onClick={() => removeRow(rowIndex)}>Remove</s-button>
        </s-table-cell>
    </s-table-row>
))}

Original post here: Shopify Forum

Hey @briniselyes - thanks for flagging this. I was able to reproduce the behaviour you mentioned here - looking into this and I’ll loop back once I have more info. :slight_smile: