---
title: Migrate FormLayout from Polaris React
description: >-
  Replace the Polaris React FormLayout component with a responsive s-grid
  composition.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/form-layout
  md: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/form-layout.md
api_name: app-home
---

# Migrate Form​Layout from Polaris React

Replace Polaris React `FormLayout` and `FormLayout.Group` with [`s-grid`](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/grid). `s-grid` controls layout only. Keep form submission on a native `form` element, and keep labels, values, validation, and errors on each field.

***

## Migrate a form layout

The following example preserves a two-column name group that collapses to one column when its container is 500 pixels wide or narrower. The email field remains a full-width row.

## Migrating a contact form layout

##### Polaris web components

```tsx
function MerchantContactForm() {
  return (
    <form data-save-bar>
      <s-grid gridTemplateColumns="1fr" gap="base">
        <s-query-container>
          <s-grid
            gridTemplateColumns="@container (inline-size > 500px) 1fr 1fr, 1fr"
            gap="base"
          >
            <s-text-field
              label="First name"
              name="firstName"
              autocomplete="given-name"
            />
            <s-text-field
              label="Last name"
              name="lastName"
              autocomplete="family-name"
            />
          </s-grid>
        </s-query-container>
        <s-email-field
          label="Email"
          name="email"
          autocomplete="email"
        />
      </s-grid>
    </form>
  );
}
```

##### Polaris React

```tsx
import {FormLayout, TextField} from '@shopify/polaris';
import {useState} from 'react';

export function MerchantContactForm() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [email, setEmail] = useState('');

  return (
    <FormLayout>
      <FormLayout.Group>
        <TextField
          label="First name"
          value={firstName}
          onChange={setFirstName}
          autoComplete="given-name"
        />
        <TextField
          label="Last name"
          value={lastName}
          onChange={setLastName}
          autoComplete="family-name"
        />
      </FormLayout.Group>
      <TextField
        label="Email"
        type="email"
        value={email}
        onChange={setEmail}
        autoComplete="email"
      />
    </FormLayout>
  );
}
```

The migrated fields use `name` attributes so that the native form includes their values in `FormData`. The example also adds `data-save-bar` so that the form participates in the App Bridge save-bar pattern. Omit `data-save-bar` if the form doesn't track unsaved changes.

***

## Replace Form​Layout and Form​Layout.​Group

| Polaris React | Polaris web components | Migration notes |
| - | - | - |
| `FormLayout` | An outer `s-grid` with `gridTemplateColumns="1fr"` | Creates the vertical sequence of rows. |
| `FormLayout.Group` | A nested `s-grid` | Creates columns for related fields. |
| Space between fields | `gap` | Choose a documented Polaris spacing value. |
| Responsive grouping | `s-query-container` and a container query in `gridTemplateColumns` | Base the field layout on the available container width, not the viewport width. |
| `condensed` group | An explicit `gridTemplateColumns` value | Choose column widths for the form instead of carrying the visual preset forward. |
| Group `title` | An `s-heading` before the nested grid | Keep the heading and fields together in the outer grid. |
| Group `helpText` | An `s-paragraph` before the nested grid | Keep instructions visible and adjacent to the fields they describe. |

Don't replace `FormLayout` with `s-stack`. A grid expresses field columns and their responsive collapse without adding layout wrappers around each field.

***

## Preserve form behavior

`s-grid` doesn't own form state. Migrate each field's behavior with the field component:

* Add a stable `name` to every value that the form submits.
* Preserve `autocomplete`, required state, constraints, and validation messages.
* Preserve controlled state when app logic needs it. Use native form values when React doesn't need to own each keystroke.
* Keep source order aligned with the intended reading and keyboard order. CSS columns shouldn't reorder fields.
* Keep a group heading or instructions adjacent to the group instead of placing them in placeholder text.

For field-specific property mappings, refer to the migration guide for that field, such as [`TextField`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/text-field) or [`Select`](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/select).

***

## Test the migration

* Resize the form's container through each layout breakpoint, including when the app navigation changes the available width.
* Verify the reading and keyboard order in both the one-column and multi-column layouts.
* Submit the form, and confirm that `FormData` contains every named field.
* Verify field errors and instructions remain associated with the correct field.
* If you use `data-save-bar`, then test save, discard, validation failure, and navigation with unsaved changes.

***

## Remove Polaris React

After every `FormLayout` call site is migrated, remove the `FormLayout` import and layout wrappers used only by it. Remove `@shopify/polaris` only after no other route in scope imports it.

***

## Related guidance

* [Grid component](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/grid)
* [Query container component](https://shopify.dev/docs/api/app-home/web-components/layout-and-structure/query-container)
* [Save bar](https://shopify.dev/docs/api/app-home/app-bridge-web-components/save-bar)

***
