Migrate Form from Polaris React
Replace Polaris React Form with the native form element. Polaris web-component fields are form-associated, so named fields participate in FormData, submit, and reset behavior.
If the app renders controlled Polaris web-component fields through React, upgrade to React 19 first. React 18 doesn't provide the custom-element property and event behavior this example relies on. If you can't upgrade yet, keep the controlled Polaris React fields during this migration slice.
Anchor to Migrate a settings formMigrate a settings form
The following example submits immediately with a native submit button and shows pending and form-level error states. Add data-save-bar only when the page also needs unsaved-change protection.
Migrating a product settings form
Polaris web components
import {useState, type FormEvent} from 'react';
type FormSubmitEvent =
| FormEvent<HTMLFormElement>
| (SubmitEvent & {currentTarget: HTMLFormElement});
export function ProductSettings({initialTitle, saveProduct}) {
const [pending, setPending] = useState(false);
const [submitError, setSubmitError] = useState('');
async function handleSubmit(event: FormSubmitEvent) {
event.preventDefault();
if (pending) return;
setPending(true);
setSubmitError('');
try {
await saveProduct(new FormData(event.currentTarget));
shopify.toast.show('Product saved');
} catch {
setSubmitError("Product couldn't be saved. Check the fields and try again.");
} finally {
setPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
<s-section heading="Product settings">
<s-stack gap="base">
{submitError && (
<s-banner tone="critical" heading="Fix the following error">
{submitError}
</s-banner>
)}
<s-text-field
label="Title"
name="title"
defaultValue={initialTitle}
autocomplete="off"
required
/>
<s-button type="submit" variant="primary" loading={pending}>
Save product
</s-button>
</s-stack>
</s-section>
</form>
);
}Polaris React
import {Form, TextField} from '@shopify/polaris';
export function ProductSettings({title, setTitle, saveProduct}) {
return (
<Form
noValidate
preventDefault
onSubmit={() => saveProduct({title})}
>
<TextField
label="Title"
name="title"
value={title}
onChange={setTitle}
autoComplete="off"
/>
<button type="submit">Save</button>
</Form>
);
}Preview
Anchor to Replace Form propertiesReplace Form properties
| Polaris React | Native form | Migration notes |
|---|---|---|
onSubmit | onSubmit | Read named controls with new FormData(event.currentTarget). |
preventDefault | event.preventDefault() inside onSubmit | Use it only when app code handles persistence instead of native navigation. |
action and method | action and method | Keep native submission when the server endpoint owns the workflow. |
acceptCharset, encType, name, and target | Same native attributes | Preserve them only when the endpoint depends on them. |
autoComplete boolean | autoComplete="on" or autoComplete="off" | Prefer field-specific autocomplete tokens when available. |
noValidate | noValidate | If set, render actionable field errors from server and client validation. |
implicitSubmit | Native Enter-key submission | Keep standard behavior unless the old form intentionally disabled it for a documented product rule. |
children | Native and Polaris web-component form controls | Give every submitted control a stable name. |
Don't add preventDefault() and then forget to submit. The handler must set pending state, call the backend, render field or form errors, and confirm success.
Anchor to Handle values and validationHandle values and validation
Use uncontrolled fields with defaultValue when the browser and FormData can own the draft. Use controlled value only when the UI must react to every change. Don't mix a controlled value with a second object that becomes the submission source.
Put a single-field validation message on that field's error property. Use s-banner for a submission failure that affects the complete form. After a failed submit, preserve entered values and focus the first invalid field or the form-level error summary.
When the server returns errors, map them by stable field name. Don't rely on translated labels as error keys.
Anchor to Add unsaved-change protection when neededAdd unsaved-change protection when needed
Add data-save-bar to the native form when merchants can leave with unsaved changes. The form's submit event handles Save, and reset handles Discard. Add data-discard-confirmation when discarding is risky.
Use the programmatic Save Bar API instead when dirty state isn't represented by native form controls. Don't use automatic and programmatic save-bar control for the same form.
Anchor to Test the migrationTest the migration
- Submit with the primary button and Enter from each eligible field.
- Verify
FormDatacontains every named value, including web-component fields, choices, and dates. - Exercise client and server validation, pending state, duplicate submission, failure, retry, and success.
- Reset or discard controlled and uncontrolled fields.
- Navigate away with clean and dirty state when the form uses a save bar.
Anchor to Remove Polaris ReactRemove Polaris React
After every form is migrated, remove Form, callback adapters used only for Polaris value signatures, and duplicate form-state helpers. Remove @shopify/polaris only after no other route in scope imports it.