---
title: Migrate ChoiceList from Polaris React
description: >-
  Replace Polaris React ChoiceList descriptors with s-choice-list and explicit
  s-choice children.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/choice-list
  md: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/choice-list.md
api_name: app-home
---

# Migrate Choice​List from Polaris React

Replace Polaris React `ChoiceList` with [`s-choice-list`](https://shopify.dev/docs/api/app-home/web-components/forms/choice-list) and explicit `s-choice` children. Preserve whether the field accepts one value or multiple values, and keep the selected values in the same form or app state that performs the operation.

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 choice list

The destination renders each choice directly instead of accepting a `choices` descriptor array.

## Migrating multiple checkout choices

##### Polaris web components

```html
<s-choice-list label="Checkout options" name="checkout" multiple>
  <s-choice value="shipping" selected>
    Use the shipping address as the billing address by default
    <s-text slot="details">
      Reduces the number of fields required to check out. The billing address
      can still be edited.
    </s-text>
  </s-choice>
  <s-choice value="confirmation">
    Require a confirmation step
    <s-text slot="details">
      Customers must review their order details before purchasing.
    </s-text>
  </s-choice>
</s-choice-list>
```

##### Polaris React

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

export function CheckoutChoices({
  selectedChoices = ['shipping'],
  onChange,
}) {
  return (
    <ChoiceList
      title="Checkout options"
      allowMultiple
      selected={selectedChoices}
      onChange={onChange}
      choices={[
        {
          label: 'Use the shipping address as the billing address by default',
          value: 'shipping',
          helpText: 'Reduces the number of fields required to check out. The billing address can still be edited.',
        },
        {
          label: 'Require a confirmation step',
          value: 'confirmation',
          helpText: 'Customers must review their order details before purchasing.',
        },
      ]}
    />
  );
}
```

***

## Replace choice descriptors

| Polaris React | Polaris web components | Migration notes |
| - | - | - |
| `title` | `label` on `s-choice-list` | Keep a group label that describes the decision. |
| `titleHidden` | `labelAccessibilityVisibility="exclusive"` | Visually hide only a redundant label. |
| `choices` | Explicit `s-choice` children | Move each descriptor's label to child content and keep its stable `value`. |
| Choice `helpText` | `s-text slot="details"` inside that `s-choice` | Keep explanation with the option it describes. |
| Choice `disabled` | `disabled` on that `s-choice` | Disable only the unavailable option. |
| `allowMultiple` | `multiple` | Omit it for a single-choice radio group. |
| `selected` | `values` on `s-choice-list` | Pass the controlled string array. For initial-only selection, use `selected` on child choices. |
| `onChange(selected, name)` | `onChange(event)` | Read the selected array from `event.currentTarget.values`. |
| `error` | `error` | Keep the message at group level so it describes the complete decision. |
| `disabled` | `disabled` on `s-choice-list` | Disable the group and preserve the condition that controls availability. |

Don't create a second descriptor array only to map it into `s-choice` elements. Render from the domain options that the app already uses, and derive selected state from one source.

***

## Choose single or multiple selection

For single selection, omit `multiple` and store zero or one value in the `values` array. If the old code used individual `RadioButton` components, migrate the complete group to one choice list so the shared label and keyboard behavior remain intact.

For multiple selection, set `multiple` and treat `values` as an unordered set unless option order has domain meaning. Validate combinations at group level, such as requiring at least one value or preventing mutually exclusive choices.

***

## Test the migration

* Select every option with pointer and keyboard input.
* Verify single-choice fields never retain more than one value.
* Verify multiple-choice values submit with the expected `name` and stable values.
* Test per-choice and whole-group disabled states.
* Trigger and announce group errors, including required and invalid combinations.
* Reload controlled forms and confirm the selected values restore correctly.

***

## Remove Polaris React

After every call site is migrated, remove `ChoiceList`, descriptor-only helpers, and callback adapters. Remove `@shopify/polaris` only after no other route in scope imports it.

***

## Related guidance

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

***
