---
title: Radio
description: Migration guide for Radio from HeroUI v2 to v3
---

<Callout type="info">
  Refer to the [v3 Radio documentation](/docs/react/components/radio-group) for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
</Callout>

## Structure Changes

In v2, Radio used a simple structure with props:

```tsx
import { RadioGroup, Radio } from "@heroui/react";

export default function App() {
  return (
    <RadioGroup label="Select city">
      <Radio value="london">London</Radio>
      <Radio value="tokyo" description="Capital of Japan">Tokyo</Radio>
    </RadioGroup>
  );
}
```

In v3, Radio requires compound components:

```tsx
import { RadioGroup, Radio, Label, Description } from "@heroui/react";

export default function App() {
  return (
    <RadioGroup>
      <Label>Select city</Label>
      <Radio value="london">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          London
        </Radio.Content>
      </Radio>
      <Radio value="tokyo">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          Tokyo
        </Radio.Content>
        <Description>Capital of Japan</Description>
      </Radio>
    </RadioGroup>
  );
}
```

## Key Changes

### 1. Component Structure

**v2:** Simple Radio with children as label  
**v3:** Compound components: `Radio.Content`, `Radio.Control`, `Radio.Indicator`

### 2. Prop Changes

| v2 Prop | v3 Location | Notes |
|---------|-------------|-------|
| `onValueChange` | `onChange` | Renamed event handler |
| `label` (on RadioGroup) | — | Use `Label` component |
| `description` (on Radio) | — | Use `Description` component as a sibling of `Radio.Content` |
| `size` | — | Removed (use Tailwind CSS) |
| `color` | — | Removed (use Tailwind CSS) |
| `classNames` | — | Use `className` props on individual components |
| `disableAnimation` | — | Removed (animations handled differently) |
| — | `variant` | New prop on `RadioGroup`: `"primary"` (default) or `"secondary"` for lower emphasis styling |
| — | `isReadOnly` | New prop on `RadioGroup`: prevents value changes while keeping the group focusable |

## Migration Examples

### Form Validation

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    {/* With description */}
    <RadioGroup label="Select city">
      <Radio value="london" description="Capital of England">
        London
      </Radio>
    </RadioGroup>
    
    {/* With validation */}
    <RadioGroup
      isInvalid
      errorMessage="Please select an option"
      label="Select city"
    >
      <Radio value="london">London</Radio>
    </RadioGroup>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { Label, Description, FieldError } from "@heroui/react";

    {/* With description */}
    <RadioGroup>
      <Label>Select city</Label>
      <Radio value="london">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          London
        </Radio.Content>
        <Description>Capital of England</Description>
      </Radio>
    </RadioGroup>
    
    {/* With validation */}
    <RadioGroup isInvalid>
      <Label>Select city</Label>
      <Radio value="london">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          London
        </Radio.Content>
      </Radio>
      <FieldError>Please select an option</FieldError>
    </RadioGroup>
    ```
  </Tab>
</Tabs>

### Controlled

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    import { useState } from "react";

    const [selected, setSelected] = useState("london");

    <RadioGroup
      value={selected}
      onValueChange={setSelected}
    >
      <Radio value="london">London</Radio>
      <Radio value="tokyo">Tokyo</Radio>
    </RadioGroup>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    import { useState } from "react";

    const [selected, setSelected] = useState("london");

    <RadioGroup value={selected} onChange={setSelected}>
      <Label>Select city</Label>
      <Radio value="london">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          London
        </Radio.Content>
      </Radio>
      <Radio value="tokyo">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          Tokyo
        </Radio.Content>
      </Radio>
    </RadioGroup>
    ```
  </Tab>
</Tabs>

### Horizontal Orientation

<Tabs items={["v2", "v3"]}>
  <Tab value="v2">
    ```tsx
    <RadioGroup orientation="horizontal" label="Select city">
      <Radio value="london">London</Radio>
      <Radio value="tokyo">Tokyo</Radio>
    </RadioGroup>
    ```
  </Tab>
  <Tab value="v3">
    ```tsx
    <RadioGroup orientation="horizontal">
      <Label>Select city</Label>
      <Radio value="london">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          London
        </Radio.Content>
      </Radio>
      <Radio value="tokyo">
        <Radio.Content>
          <Radio.Control>
            <Radio.Indicator />
          </Radio.Control>
          Tokyo
        </Radio.Content>
      </Radio>
    </RadioGroup>
    ```
  </Tab>
</Tabs>

### Variants

v3 introduces a `variant` prop on `RadioGroup` with `"primary"` (default) and `"secondary"` options:

```tsx
{/* Primary variant (default) */}
<RadioGroup variant="primary">
  <Label>Select city</Label>
  <Radio value="london">
    <Radio.Content>
      <Radio.Control>
        <Radio.Indicator />
      </Radio.Control>
      London
    </Radio.Content>
  </Radio>
</RadioGroup>

{/* Secondary variant - lower emphasis, suitable for Surface components */}
<RadioGroup variant="secondary">
  <Label>Select city</Label>
  <Radio value="london">
    <Radio.Content>
      <Radio.Control>
        <Radio.Indicator />
      </Radio.Control>
      London
    </Radio.Content>
  </Radio>
</RadioGroup>
```

### Read Only

v3 supports `isReadOnly` on `RadioGroup`, which prevents value changes while keeping the group focusable:

```tsx
<RadioGroup isReadOnly defaultValue="london">
  <Label>Select city</Label>
  <Radio value="london">
    <Radio.Content>
      <Radio.Control>
        <Radio.Indicator />
      </Radio.Control>
      London
    </Radio.Content>
  </Radio>
  <Radio value="tokyo">
    <Radio.Content>
      <Radio.Control>
        <Radio.Indicator />
      </Radio.Control>
      Tokyo
    </Radio.Content>
  </Radio>
</RadioGroup>
```

## Component Anatomy

The v3 Radio follows this structure:

```
RadioGroup (Root)
  ├── Label (optional)
  ├── Radio
  │   ├── Radio.Content (the clickable label)
  │   │   ├── Radio.Control
  │   │   │   └── Radio.Indicator
  │   │   └── Label
  │   └── Description (optional, sibling)
  └── FieldError (optional)
```

## Summary

1. **Component Structure**: Must use compound components (`Radio.Content`, `Radio.Control`, `Radio.Indicator`)
2. **Label Handling**: `label` prop removed - use `Label` component
3. **Description Handling**: `description` prop removed - use `Description` component as a sibling of `Radio.Content`
4. **Event Handler**: `onValueChange` → `onChange`
5. **Styling Props Removed**: `size`, `color` - use Tailwind CSS
6. **ClassNames Removed**: Use `className` props on individual components
7. **Error Message**: Use `FieldError` component instead of `errorMessage` prop
8. **New `variant` Prop**: `RadioGroup` supports `"primary"` (default) and `"secondary"` for lower emphasis styling
9. **New `isReadOnly` Prop**: `RadioGroup` supports `isReadOnly` to prevent value changes while keeping the group focusable
