# RadioGroup

A set of options where exactly one is chosen, walked with the arrow keys.

```slint
import { RadioGroup, RadioItem } from "@glint/components/radio-group.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    in-out property <int> selected-plan: 1;

    VerticalLayout {
        alignment: center;
        spacing: 16px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            RadioGroup {
                selected <=> root.selected-plan;
                items: [
                    { value: "starter", label: "Starter Tier", description: "Best for individual developers." },
                    { value: "pro", label: "Professional Tier", description: "Advanced tooling and priority support." },
                    { value: "enterprise", label: "Enterprise Tier", description: "Dedicated infrastructure." },
                ];
            }
        }

        Text {
            text: "Selected index: " + root.selected-plan;
            color: Tokens.color-muted-foreground;
            font-size: Tokens.typography-body-sm-size;
            horizontal-alignment: center;
        }
    }
}
```

## Usage

```slint
import { RadioGroup, RadioItem } from "@glint/components/radio-group.slint";

export component AppWindow inherits Window {
    in-out property <int> billing-cycle: 0;

    VerticalLayout {
        alignment: center;

        RadioGroup {
            selected <=> root.billing-cycle;
            items: [
                { value: "monthly", label: "Monthly billing" },
                { value: "yearly", label: "Yearly billing (Save 20%)" },
            ];
            changed(index) => {
                // handle choice
            }
        }
    }
}
```

`RadioGroup` presents a mutually exclusive single-selection set. Keyboard navigation uses arrow keys to cycle through options, committing the selection immediately.

## Examples

### Orientations

The `orientation` property controls the layout direction of items:

- `RadioOrientation.vertical`: Stacks options in a column (default).
- `RadioOrientation.horizontal`: Places options side by side in a row.

```slint
import { RadioGroup, RadioOrientation } from "@glint/components/radio-group.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 340px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        spacing: 24px;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            VerticalLayout {
                spacing: 20px;

                RadioGroup {
                    orientation: RadioOrientation.vertical;
                    items: [
                        { value: "v1", label: "Vertical Option 1" },
                        { value: "v2", label: "Vertical Option 2" },
                    ];
                }

                RadioGroup {
                    orientation: RadioOrientation.horizontal;
                    items: [
                        { value: "h1", label: "Option A" },
                        { value: "h2", label: "Option B" },
                        { value: "h3", label: "Option C" },
                    ];
                }
            }
        }
    }
}
```

### Descriptions and disabled options

Each item can declare a secondary `description` and individual `disabled` state. Arrow keys skip disabled options automatically.

```slint
import { RadioGroup } from "@glint/components/radio-group.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            RadioGroup {
                items: [
                    { value: "us", label: "US East (N. Virginia)", description: "Low latency region" },
                    { value: "eu", label: "EU Central (Frankfurt)", description: "GDPR compliant" },
                    { value: "ap", label: "AP East (Tokyo)", description: "Region currently at capacity", disabled: true },
                ];
            }
        }
    }
}
```

### Custom Radio composition

For complex layouts (such as rich selectable cards), compose individual `Radio` components by binding their `checked` and `clicked` handlers to shared state.

```slint
import { Radio } from "@glint/components/radio-group.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 300px;
    background: Tokens.color-background;

    in-out property <int> chosen: 0;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            VerticalLayout {
                spacing: 12px;

                Radio {
                    label: "Standard Shipping";
                    description: "Delivered in 3-5 business days.";
                    checked: root.chosen == 0;
                    clicked => { root.chosen = 0; }
                }

                Radio {
                    label: "Express Shipping";
                    description: "Delivered next business day.";
                    checked: root.chosen == 1;
                    clicked => { root.chosen = 1; }
                }
            }
        }
    }
}
```

## API Reference

### Properties

| Property      | Type                  | Default                     | Description                                                                                                                                                                                              |
| ------------- | --------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`       | `in [RadioItem]`      | no default                  | Options shown in the group.                                                                                                                                                                              |
| `selected`    | `in-out int`          | `0`                         | Two-way; index of the chosen item.                                                                                                                                                                       |
| `disabled`    | `in bool`             | `false`                     | When true, the whole group dims and stops responding. An option may also refuse on its own; see `RadioItem.disabled`.                                                                                    |
| `invalid`     | `in bool`             | `false`                     | When true, the options wear the destructive border and the group announces itself invalid. The message that explains the error belongs to the surrounding `Field` — bind this to that Field's `invalid`. |
| `orientation` | `in RadioOrientation` | `RadioOrientation.vertical` | `vertical` (default) or `horizontal`.                                                                                                                                                                    |

### Callbacks

| Callback       | Description                          |
| -------------- | ------------------------------------ |
| `changed(int)` | Fired with the new `selected` index. |

### Enums

| Enum               | Values                   |
| ------------------ | ------------------------ |
| `RadioOrientation` | `vertical`, `horizontal` |

### Radio

Individual radio button primitive used inside `RadioGroup` or for custom choice cards.

### Properties

| Property      | Type        | Default    | Description                                                                                                                                                                                                                                                                                                          |
| ------------- | ----------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label`       | `in string` | no default | Label text rendered next to the circle. Also the option's accessible name.                                                                                                                                                                                                                                           |
| `description` | `in string` | no default | Second line under the label, in the label's own column.                                                                                                                                                                                                                                                              |
| `checked`     | `in bool`   | `false`    | Whether this option is the chosen one. Bound, never written here.                                                                                                                                                                                                                                                    |
| `disabled`    | `in bool`   | `false`    | When true, the option dims and stops responding.                                                                                                                                                                                                                                                                     |
| `invalid`     | `in bool`   | `false`    | When true, the circle wears the destructive border. Only the border: an option is a row of a group, never the control a `Field` binds, so the group is what announces the error — saying it here too would repeat it on every option a user arrows past. CONTEXT.md "Field": controls carry only visual error state. |
| `show-focus`  | `in bool`   | `false`    | When true, the focus ring renders — the set draws it on the option the keyboard stands on, since the set is what owns the keyboard.                                                                                                                                                                                  |
| `index`       | `in int`    | `0`        | Position in the set, announced as the option's index.                                                                                                                                                                                                                                                                |

### Callbacks

| Callback    | Description                                                                                                                   |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `clicked()` | Fired when this option is activated — by the pointer or by assistive technology. The set is what turns that into a selection. |

## Accessibility

- **Radio group role.** `RadioGroup` publishes `accessible-role: radio-group` containing child `radio-button` items.
- **Focus delegation.** `accessible-delegate-focus` routes assistive technology announcements to the currently selected item.
- **Keyboard navigation.** `↑`/`↓` and `←`/`→` cycle through options with wrap-around, stepping over disabled options.
- **`invalid` announces the word “Invalid”.** Set on the group, it rides the description channel ADR-0013 opened because Slint 1.17 publishes no `accessible-invalid`. In the current implementation, the group’s `accessible-description` is only `"Invalid"`; it is not appended to another description. A red border is not the announcement.
