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

# Migrate Radio​Button from Polaris React

Replace a group of Polaris React `RadioButton` components with one [`s-choice-list`](https://shopify.dev/docs/api/app-home/web-components/forms/choice-list) containing `s-choice` children. The choice list owns the group label, selected value, error, and keyboard behavior.

Don't migrate a radio button in isolation. Find every radio with the same `name` or shared state and migrate the complete decision.

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 fields in place during this migration slice.

***

## Migrate a radio group

## Migrating product visibility radios

##### Polaris web components

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

export function ProductVisibilityChoice() {
  const [visibility, setVisibility] = useState('hidden');

  return (
    <s-choice-list
      label="Product visibility"
      name="visibility"
      values={[visibility]}
      onChange={(event) =>
        setVisibility(event.currentTarget.values[0] ?? '')
      }
    >
      <s-choice value="hidden">Hidden</s-choice>
      <s-choice value="optional">Optional</s-choice>
      <s-choice value="required">Required</s-choice>
    </s-choice-list>
  );
}
```

##### Polaris React

```tsx
import {useState} from 'react';
import {RadioButton} from '@shopify/polaris';

export function ProductVisibility() {
  const [visibility, setVisibility] = useState('hidden');

  return (
    <fieldset>
      <legend>Product visibility</legend>
      {['hidden', 'optional', 'required'].map((value) => (
        <RadioButton
          key={value}
          id={`visibility-${value}`}
          name="visibility"
          label={value[0].toUpperCase() + value.slice(1)}
          value={value}
          checked={visibility === value}
          onChange={(_checked) => setVisibility(value)}
        />
      ))}
    </fieldset>
  );
}
```

***

## Replace Radio​Button properties

| Polaris React | Polaris web components | Migration notes |
| - | - | - |
| Shared `name` | `name` on `s-choice-list` | Use one stable form field name for the group. |
| `label` | Text content of `s-choice` | Keep each option label self-contained. |
| `value` | `value` on `s-choice` | Use stable backend values, not translated labels. |
| `checked` | `values` on the containing `s-choice-list` | For an initial uncontrolled value, set `selected` on one child. |
| `onChange(checked, id)` | `onChange(event)` on `s-choice-list` | Read the single selected value from `event.currentTarget.values`. |
| `disabled` | `disabled` on one `s-choice` or the whole list | Preserve whether one option or the complete decision is unavailable. |
| `helpText` | `s-text slot="details"` inside that `s-choice` | Keep option-specific guidance with the option. |
| `labelHidden` | Keep a visible choice label | A radio option generally needs visible text; use an exclusive group label only when nearby context is truly redundant. |
| `ariaDescribedBy` | Choice or list `details` and `error` | Prefer documented relationships instead of carrying an external ID forward. |
| `fill` and inherited bleed props | Remove | Let the containing grid or stack own layout. |
| `tone="magic"` | Remove | Preserve meaning in the label and content rather than an unsupported visual treatment. |

Omit `multiple` so the choice list behaves as a radio group. If merchants can choose more than one value, the old radios modelled the task incorrectly; migrate to a multiple choice list or checkboxes.

A single radio that toggles a boolean isn't a complete radio group. Replace it with `s-checkbox` when the choice is independently on or off.

***

## Reconnect controlled state

For controlled selection, pass an array containing zero or one value to `values`. On change, read `event.currentTarget.values[0]` and update the same state used by validation and submission.

Put a required or invalid-selection message on the choice list's `error` property. Don't attach separate errors to every option when the validation rule applies to the group.

***

## Test the migration

* Move through every option with arrow keys and select with keyboard and pointer input.
* Confirm the group never retains more than one value.
* Submit, reload, and edit existing data to verify value serialization.
* Exercise option-level and group-level disabled states.
* Verify group label, option details, required state, and errors are announced together.

***

## Remove Polaris React

After each complete group is migrated, remove `RadioButton`, shared checked-state adapters, and manual fieldset markup replaced by the choice list. 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 ChoiceList from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/choice-list)
* [Migrate Checkbox from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/checkbox)

***
