---
title: Migrate Checkbox from Polaris React
description: >-
  Replace Polaris React Checkbox with s-checkbox while preserving controlled
  state, validation, form values, and partial selection.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/checkbox
  md: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/checkbox.md
api_name: app-home
---

# Migrate Checkbox from Polaris React

Replace Polaris React `Checkbox` with [`s-checkbox`](https://shopify.dev/docs/api/app-home/web-components/forms/checkbox). Keep the checked value in app state when other controls or backend operations depend on it, and use the field's native form properties when the value is submitted with a form.

If the app renders this controlled field 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, leave the controlled Polaris React field in place during this migration slice.

***

## Migrate a checkbox

This example preserves a required acknowledgement and its visible validation message.

## Migrating a required acknowledgement

##### Polaris web components

```tsx
import {useState} from 'react';

export function TermsCheckbox() {
  const [accepted, setAccepted] = useState(false);

  return (
    <s-checkbox
      label="I agree to the terms"
      checked={accepted}
      error={accepted ? undefined : 'You must accept the terms to continue'}
      onChange={(event) => setAccepted(event.currentTarget.checked)}
    />
  );
}
```

##### Polaris React

```tsx
import {Checkbox} from '@shopify/polaris';

export function TermsCheckbox({
  accepted = false,
  error = 'You must accept the terms to continue',
  onChange,
}) {
  return (
    <Checkbox
      label="I agree to the terms"
      checked={accepted}
      error={error}
      onChange={onChange}
    />
  );
}
```

***

## Map checkbox properties

| Polaris React | Polaris web components | Migration notes |
| - | - | - |
| `label` | `label` | Pass a string that describes the choice. Don't move the only label into surrounding text. |
| `labelHidden` | Visible `label`, or `accessibilityLabel` when adjacent content already identifies the control | `s-checkbox` doesn't expose `labelAccessibilityVisibility`. Prefer a visible label. |
| `helpText` | `details` | Keep instructions next to the checkbox. |
| `error` | `error` | Pass the validation message as a string. A boolean error state without a message isn't sufficient. |
| `checked={true}` | `checked` | Use for controlled state. Read the next value from `event.currentTarget.checked`. |
| `checked="indeterminate"` | `indeterminate` | Keep `checked` and `indeterminate` as separate values. Indeterminate changes appearance, not the submitted value. |
| `disabled` | `disabled` | Preserve the reason the choice can't be changed. Don't use disabled state to hide an authorization failure. |
| `id`, `name`, and `value` | `id`, `name`, and `value` | Keep stable form names and backend values. |
| `onChange(checked, id)` | `onChange(event)` | Read `event.currentTarget.checked` and use the component's known ID when the handler needs it. |

Use `defaultChecked` instead of `checked` only when the checkbox is intentionally uncontrolled. Don't combine `defaultChecked` with React state that expects to update the rendered value.

Like a native checkbox, an unchecked `s-checkbox` contributes no entry to `FormData`. If the backend must receive an explicit false value, normalize the missing key on the server or append a fallback value in the submit handler. Don't add a hidden input with the same name unless the backend intentionally supports receiving both values when the checkbox is checked.

***

## Preserve partial selection

For a select-all checkbox, calculate these values from the selected resource IDs:

1. Set `checked` when every item in the defined scope is selected.
2. Set `indeterminate` when at least one, but not every, item is selected.
3. Update the same selected-ID state from the select-all and row checkbox handlers.
4. Define whether the scope means the visible page, the current query, or every resource.

Don't store selection only in the checkbox element. Bulk-action payloads, selected counts, and post-action cleanup must use the same app state.

***

## Test the migration

* Toggle the checkbox with pointer, keyboard, and label activation.
* Verify controlled and initial values don't drift after rerenders or form resets.
* Submit checked and unchecked states and confirm the backend treats the missing unchecked value as intended.
* Trigger, announce, and clear validation errors.
* Test checked, unchecked, and indeterminate selection calculations.

***

## Remove Polaris React

After every call site is migrated, remove the `Checkbox` import and adapters that convert `(checked, id)` callbacks. Remove `@shopify/polaris` only after no other route in scope imports it.

***

## Related guidance

* [Checkbox component](https://shopify.dev/docs/api/app-home/web-components/forms/checkbox)
* [Migrate ChoiceList from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/choice-list)
* [Migrate IndexTable from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/index-table)

***
