---
title: Radio
description: Radio buttons for mutually exclusive choices.
icon: circle-dot
---

import { ComponentPreview } from "/snippets/component-preview.mdx";

Radio buttons let users select exactly one option from a set. Each `Radio` represents a single choice; wrap them in a `RadioGroup` to link them together and track the selected value.

## Basic Usage

A `RadioGroup` is a container that holds `Radio` children. Each radio needs a `value` (what gets stored when selected) and a `label` (what the user sees). The group tracks which radio is selected and stores its `value` string in state:

<ComponentPreview json={{"view":{"cssClass":"w-fit mx-auto","name":"radiogroup_1","type":"RadioGroup","children":[{"name":"radio_1","type":"Radio","value":"sm","label":"Small","checked":false,"disabled":false,"required":false},{"name":"radio_2","type":"Radio","value":"md","label":"Medium","checked":false,"disabled":false,"required":false},{"name":"radio_3","type":"Radio","value":"lg","label":"Large","checked":false,"disabled":false,"required":false}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmFkaW8sIFJhZGlvR3JvdXAKCndpdGggUmFkaW9Hcm91cChjc3NfY2xhc3M9InctZml0IG14LWF1dG8iKToKICAgIFJhZGlvKHZhbHVlPSJzbSIsIGxhYmVsPSJTbWFsbCIpCiAgICBSYWRpbyh2YWx1ZT0ibWQiLCBsYWJlbD0iTWVkaXVtIikKICAgIFJhZGlvKHZhbHVlPSJsZyIsIGxhYmVsPSJMYXJnZSIpCg==">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Radio, RadioGroup

with RadioGroup(css_class="w-fit mx-auto"):
    Radio(value="sm", label="Small")
    Radio(value="md", label="Medium")
    Radio(value="lg", label="Large")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "w-fit mx-auto",
    "name": "radiogroup_1",
    "type": "RadioGroup",
    "children": [
      {
        "name": "radio_1",
        "type": "Radio",
        "value": "sm",
        "label": "Small",
        "checked": false,
        "disabled": false,
        "required": false
      },
      {
        "name": "radio_2",
        "type": "Radio",
        "value": "md",
        "label": "Medium",
        "checked": false,
        "disabled": false,
        "required": false
      },
      {
        "name": "radio_3",
        "type": "Radio",
        "value": "lg",
        "label": "Large",
        "checked": false,
        "disabled": false,
        "required": false
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Pre-selected Option

Set `checked=True` on a radio to have it selected when the component first renders:

<ComponentPreview json={{"view":{"cssClass":"w-fit mx-auto","name":"radiogroup_2","type":"RadioGroup","children":[{"name":"radio_4","type":"Radio","value":"sm","label":"Small","checked":false,"disabled":false,"required":false},{"name":"radio_5","type":"Radio","value":"md","label":"Medium","checked":true,"disabled":false,"required":false},{"name":"radio_6","type":"Radio","value":"lg","label":"Large","checked":false,"disabled":false,"required":false}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmFkaW8sIFJhZGlvR3JvdXAKCndpdGggUmFkaW9Hcm91cChjc3NfY2xhc3M9InctZml0IG14LWF1dG8iKToKICAgIFJhZGlvKHZhbHVlPSJzbSIsIGxhYmVsPSJTbWFsbCIpCiAgICBSYWRpbyh2YWx1ZT0ibWQiLCBsYWJlbD0iTWVkaXVtIiwgY2hlY2tlZD1UcnVlKQogICAgUmFkaW8odmFsdWU9ImxnIiwgbGFiZWw9IkxhcmdlIikK">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Radio, RadioGroup

with RadioGroup(css_class="w-fit mx-auto"):
    Radio(value="sm", label="Small")
    Radio(value="md", label="Medium", checked=True)
    Radio(value="lg", label="Large")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "w-fit mx-auto",
    "name": "radiogroup_2",
    "type": "RadioGroup",
    "children": [
      {
        "name": "radio_4",
        "type": "Radio",
        "value": "sm",
        "label": "Small",
        "checked": false,
        "disabled": false,
        "required": false
      },
      {
        "name": "radio_5",
        "type": "Radio",
        "value": "md",
        "label": "Medium",
        "checked": true,
        "disabled": false,
        "required": false
      },
      {
        "name": "radio_6",
        "type": "Radio",
        "value": "lg",
        "label": "Large",
        "checked": false,
        "disabled": false,
        "required": false
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Disabled Options

Individual radios can be disabled while the rest of the group remains interactive. This is useful for options that exist but aren't currently available:

<ComponentPreview json={{"view":{"cssClass":"w-fit mx-auto","name":"radiogroup_3","type":"RadioGroup","children":[{"name":"radio_7","type":"Radio","value":"free","label":"Free","checked":true,"disabled":false,"required":false},{"name":"radio_8","type":"Radio","value":"pro","label":"Pro","checked":false,"disabled":false,"required":false},{"name":"radio_9","type":"Radio","value":"enterprise","label":"Enterprise","checked":false,"disabled":true,"required":false}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgUmFkaW8sIFJhZGlvR3JvdXAKCndpdGggUmFkaW9Hcm91cChjc3NfY2xhc3M9InctZml0IG14LWF1dG8iKToKICAgIFJhZGlvKHZhbHVlPSJmcmVlIiwgbGFiZWw9IkZyZWUiLCBjaGVja2VkPVRydWUpCiAgICBSYWRpbyh2YWx1ZT0icHJvIiwgbGFiZWw9IlBybyIpCiAgICBSYWRpbyh2YWx1ZT0iZW50ZXJwcmlzZSIsIGxhYmVsPSJFbnRlcnByaXNlIiwgZGlzYWJsZWQ9VHJ1ZSkK">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Radio, RadioGroup

with RadioGroup(css_class="w-fit mx-auto"):
    Radio(value="free", label="Free", checked=True)
    Radio(value="pro", label="Pro")
    Radio(value="enterprise", label="Enterprise", disabled=True)
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "w-fit mx-auto",
    "name": "radiogroup_3",
    "type": "RadioGroup",
    "children": [
      {
        "name": "radio_7",
        "type": "Radio",
        "value": "free",
        "label": "Free",
        "checked": true,
        "disabled": false,
        "required": false
      },
      {
        "name": "radio_8",
        "type": "Radio",
        "value": "pro",
        "label": "Pro",
        "checked": false,
        "disabled": false,
        "required": false
      },
      {
        "name": "radio_9",
        "type": "Radio",
        "value": "enterprise",
        "label": "Enterprise",
        "checked": false,
        "disabled": true,
        "required": false
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

## Reading the Value

Use `.rx` on the `RadioGroup` to get a reactive reference to the selected radio's `value` string. When the user clicks a different option, anything bound to `.rx` updates automatically:

<ComponentPreview json={{"view":{"cssClass":"gap-4 w-fit mx-auto","type":"Column","children":[{"name":"radiogroup_4","type":"RadioGroup","children":[{"name":"radio_10","type":"Radio","value":"heart-of-gold","label":"Heart of Gold","checked":false,"disabled":false,"required":false},{"name":"radio_11","type":"Radio","value":"bistromath","label":"Bistromath","checked":false,"disabled":false,"required":false},{"name":"radio_12","type":"Radio","value":"vogon-cruiser","label":"Vogon Constructor Fleet","checked":false,"disabled":false,"required":false}]},{"content":"Selected ship: {{ radiogroup_4 }}","type":"Text"}]}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgQ29sdW1uLCBSYWRpbywgUmFkaW9Hcm91cCwgVGV4dAoKd2l0aCBDb2x1bW4oZ2FwPTQsIGNzc19jbGFzcz0idy1maXQgbXgtYXV0byIpOgogICAgd2l0aCBSYWRpb0dyb3VwKCkgYXMgZ3JvdXA6CiAgICAgICAgUmFkaW8odmFsdWU9ImhlYXJ0LW9mLWdvbGQiLCBsYWJlbD0iSGVhcnQgb2YgR29sZCIpCiAgICAgICAgUmFkaW8odmFsdWU9ImJpc3Ryb21hdGgiLCBsYWJlbD0iQmlzdHJvbWF0aCIpCiAgICAgICAgUmFkaW8odmFsdWU9InZvZ29uLWNydWlzZXIiLCBsYWJlbD0iVm9nb24gQ29uc3RydWN0b3IgRmxlZXQiKQogICAgVGV4dChmIlNlbGVjdGVkIHNoaXA6IHtncm91cC5yeH0iKQo=">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Column, Radio, RadioGroup, Text

with Column(gap=4, css_class="w-fit mx-auto"):
    with RadioGroup() as group:
        Radio(value="heart-of-gold", label="Heart of Gold")
        Radio(value="bistromath", label="Bistromath")
        Radio(value="vogon-cruiser", label="Vogon Constructor Fleet")
    Text(f"Selected ship: {group.rx}")
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-4 w-fit mx-auto",
    "type": "Column",
    "children": [
      {
        "name": "radiogroup_4",
        "type": "RadioGroup",
        "children": [
          {
            "name": "radio_10",
            "type": "Radio",
            "value": "heart-of-gold",
            "label": "Heart of Gold",
            "checked": false,
            "disabled": false,
            "required": false
          },
          {
            "name": "radio_11",
            "type": "Radio",
            "value": "bistromath",
            "label": "Bistromath",
            "checked": false,
            "disabled": false,
            "required": false
          },
          {
            "name": "radio_12",
            "type": "Radio",
            "value": "vogon-cruiser",
            "label": "Vogon Constructor Fleet",
            "checked": false,
            "disabled": false,
            "required": false
          }
        ]
      },
      {"content": "Selected ship: {{ radiogroup_4 }}", "type": "Text"}
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

Click a radio and the text updates instantly. The `.rx` reference holds the `value` string — `"heart-of-gold"`, `"bistromath"`, etc. — not the label.

## Actions

The `on_change` parameter on `RadioGroup` fires actions when the selection changes. Inside the action, `$event` holds the newly selected `value` string:

<ComponentPreview json={{"view":{"cssClass":"gap-4 w-fit mx-auto","type":"Column","children":[{"name":"radiogroup_5","type":"RadioGroup","onChange":{"action":"setState","key":"choice","value":"You picked: {{ $event }}"},"children":[{"name":"radio_13","type":"Radio","value":"light","label":"Light","checked":false,"disabled":false,"required":false},{"name":"radio_14","type":"Radio","value":"dark","label":"Dark","checked":false,"disabled":false,"required":false},{"name":"radio_15","type":"Radio","value":"system","label":"System","checked":true,"disabled":false,"required":false}]},{"cssClass":"text-muted-foreground","content":"{{ choice | default:'Make a selection' }}","type":"Text"}]}}} playground=" ">
<CodeGroup>
```python Python icon="python"
from prefab_ui.components import Column, Radio, RadioGroup, Text
from prefab_ui.actions import SetState

with Column(gap=4, css_class="w-fit mx-auto"):
    with RadioGroup(
        on_change=SetState("choice", "You picked: {{ $event }}"),
    ):
        Radio(value="light", label="Light")
        Radio(value="dark", label="Dark")
        Radio(value="system", label="System", checked=True)
    Text(
        "{{ choice | default:'Make a selection' }}",
        css_class="text-muted-foreground",
    )
```
```json Protocol icon="brackets-curly"
{
  "view": {
    "cssClass": "gap-4 w-fit mx-auto",
    "type": "Column",
    "children": [
      {
        "name": "radiogroup_5",
        "type": "RadioGroup",
        "onChange": {"action": "setState", "key": "choice", "value": "You picked: {{ $event }}"},
        "children": [
          {
            "name": "radio_13",
            "type": "Radio",
            "value": "light",
            "label": "Light",
            "checked": false,
            "disabled": false,
            "required": false
          },
          {
            "name": "radio_14",
            "type": "Radio",
            "value": "dark",
            "label": "Dark",
            "checked": false,
            "disabled": false,
            "required": false
          },
          {
            "name": "radio_15",
            "type": "Radio",
            "value": "system",
            "label": "System",
            "checked": true,
            "disabled": false,
            "required": false
          }
        ]
      },
      {
        "cssClass": "text-muted-foreground",
        "content": "{{ choice | default:'Make a selection' }}",
        "type": "Text"
      }
    ]
  }
}
```
</CodeGroup>
</ComponentPreview>

For simple "show the current selection" cases, `.rx` is the better approach. Use `on_change` when you need to transform the value or trigger side effects.

## API Reference

<Card icon="code" title="RadioGroup Parameters">
<ParamField body="name" type="str | None" default="None">
  State key for the selected value. Auto-generated if not provided.
</ParamField>

<ParamField body="on_change" type="Action | list[Action] | None" default="None">
  Action(s) to execute when the selection changes. `$event` is the `value` string of the selected radio.
</ParamField>

<ParamField body="css_class" type="str | None" default="None">
  Additional Tailwind CSS classes.
</ParamField>
</Card>

<Card icon="code" title="Radio Parameters">
<ParamField body="value" type="str" required>
  The value stored in the group's state when this radio is selected.
</ParamField>

<ParamField body="label" type="str | None" default="None">
  Label text displayed next to the radio button.
</ParamField>

<ParamField body="checked" type="bool" default="False">
  Whether this radio is initially selected.
</ParamField>

<ParamField body="disabled" type="bool" default="False">
  Whether this radio is non-interactive.
</ParamField>

<ParamField body="required" type="bool" default="False">
  Whether a selection is required for form submission.
</ParamField>

<ParamField body="css_class" type="str | None" default="None">
  Additional Tailwind CSS classes.
</ParamField>
</Card>

## Protocol Reference

```json RadioGroup
{
  "type": "RadioGroup",
  "children?": "[Component]",
  "let?": "object",
  "name?": "string",
  "onChange?": "Action | Action[]",
  "cssClass?": "string"
}
```

```json Radio
{
  "type": "Radio",
  "value": "string (required)",
  "label?": "string",
  "name?": "string",
  "checked?": false,
  "disabled?": false,
  "required?": false,
  "cssClass?": "string"
}
```

For the complete protocol schema, see [RadioGroup](/protocol/radio-group), [Radio](/protocol/radio).
