Radio
Radio buttons allow users to select a single option from a set of mutually exclusive choices. Use radio buttons when you want to give users a choice between multiple options and only one selection is allowed.
Use on_change for requested values. The owner stores the value and calls cx.notify(). The existing on_click name remains a compatibility alias; setting either replaces the same handler, so the last call wins.
#Import
use gpui_kit::component::radio::{Radio, RadioGroup};// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Radio, RadioGroup } from "radio.slint";use iced::widget::{column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Element, Length, Task, Theme};#Usage
// A group is the caller's index written back; a standalone radio is the
// caller's boolean. Both are controlled values, the way every iced control is.
let usage = column![
RadioGroup::new()
.options(["Email", "SMS", "Push"])
.selected(app.usage)
.on_change(Message::Usage),
row![
Radio::new(app.standalone)
.label("Selected")
.on_change(Message::Standalone),
Radio::new(false)
.label("Unselected")
.on_change(Message::Standalone),
]
.spacing(16.0),
]
.spacing(12.0);#Group
struct MyView {
selected_option: Option<usize>,
}
impl Render for MyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
RadioGroup::horizontal("options")
.children(["Option 1", "Option 2", "Option 3"])
.selected_index(self.selected_option)
.on_change(cx.listener(|view, selected_index: &usize, _, cx| {
view.selected_option = Some(*selected_index);
cx.notify();
}))
}
}RadioGroup {
options: [
"Email",
"SMS",
"Push"
];
selected: 0;
}let group = RadioGroup::new()
.options(["Option 1", "Option 2", "Option 3"])
.selected(app.group)
.on_change(Message::Group);#Standalone
Radio::new("radio-option-1")
.label("Option 1")
.checked(false)
.on_change(|checked, _, _| {
println!("Radio is now: {}", checked);
})Radio {
label: "Disabled";
enabled: false;
}// A radio reports only `true`: it can never clear its own set, so the group
// cannot be emptied. Without a message the control is disabled.
let standalone = column![
Radio::new(true)
.label("Checked and enabled")
.on_change(Message::Standalone),
Radio::new(false)
.label("Unchecked and enabled")
.on_change(Message::Standalone),
]
.spacing(12.0);#Controlled Radio Button
struct MyView {
radio_checked: bool,
}
impl Render for MyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Radio::new("radio")
.label("Select this option")
.checked(self.radio_checked)
.on_change(cx.listener(|view, checked, _, cx| {
view.radio_checked = *checked;
cx.notify();
}))
}
}#Different Sizes
Radio::new("small").label("Small").xsmall()
Radio::new("medium").label("Medium") // default
Radio::new("large").label("Large").large()RadioGroup {
size: ControlSize.small;
options: [
"Small",
"Compact"
];
selected: 0;
}
RadioGroup {
options: [
"Medium",
"Default"
];
selected: 0;
}
RadioGroup {
size: ControlSize.large;
options: [
"Large",
"Roomy"
];
selected: 1;
}// The disc is 12 / 14 / 16 / 18, and a large label is text-lg.
let sizes = column![
RadioGroup::new()
.size(ControlSize::XSmall)
.options(["Extra small", "Compact"])
.selected(app.small)
.on_change(Message::Small),
RadioGroup::new()
.size(ControlSize::Medium)
.options(["Medium", "Default"])
.selected(app.medium)
.on_change(Message::Medium),
RadioGroup::new()
.size(ControlSize::Large)
.options(["Large", "Roomy"])
.selected(app.large)
.on_change(Message::Large),
]
.spacing(12.0);#Disabled State
Radio::new("disabled")
.label("Disabled option")
.disabled(true)
.checked(false)
Radio::new("disabled-checked")
.label("Disabled and checked")
.checked(true)
.disabled(true)let disabled = column![
Radio::new(false).label("Disabled option"),
Radio::new(true).label("Disabled and checked"),
]
.spacing(12.0);#Multi-line Label with Custom Content
Radio::new("custom")
.label("Primary option")
.child(
div()
.text_color(cx.theme().muted_foreground)
.child("This is additional descriptive text that provides more context.")
)
.w(px(300.))#Custom Tab Order
Radio::new("radio")
.label("Custom tab order")
.tab_index(2)
.tab_stop(true)#Settings Panel
struct SettingsView {
theme: Option<usize>, // 0: Light, 1: Dark, 2: Auto
language: Option<usize>, // 0: English, 1: Spanish, 2: French
}
impl Render for SettingsView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_6()
.child(
v_flex()
.gap_2()
.child(div().text_sm().font_semibold().child("Theme"))
.child(
RadioGroup::vertical("theme")
.child(Radio::new("light").label("Light"))
.child(Radio::new("dark").label("Dark"))
.child(Radio::new("auto").label("Auto"))
.selected_index(self.theme)
.on_change(cx.listener(|view, index, _, cx| {
view.theme = Some(*index);
cx.notify();
}))
)
)
.child(
v_flex()
.gap_2()
.child(div().text_sm().font_semibold().child("Language"))
.child(
RadioGroup::horizontal("language")
.children(["English", "Español", "Français"])
.selected_index(self.language)
.on_change(cx.listener(|view, index, _, cx| {
view.language = Some(*index);
cx.notify();
}))
)
)
}
}#Survey Form
struct SurveyView {
satisfaction: Option<usize>,
recommendation: Option<usize>,
}
impl Render for SurveyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_8()
.child(
v_flex()
.gap_3()
.child(
div()
.text_base()
.font_medium()
.child("How satisfied are you with our service?")
)
.child(
RadioGroup::vertical("satisfaction")
.child(Radio::new("very-satisfied").label("Very satisfied"))
.child(Radio::new("satisfied").label("Satisfied"))
.child(Radio::new("neutral").label("Neutral"))
.child(Radio::new("dissatisfied").label("Dissatisfied"))
.child(Radio::new("very-dissatisfied").label("Very dissatisfied"))
.selected_index(self.satisfaction)
.on_change(cx.listener(|view, index, _, cx| {
view.satisfaction = Some(*index);
cx.notify();
}))
)
)
.child(
v_flex()
.gap_3()
.child(
div()
.text_base()
.font_medium()
.child("How likely are you to recommend us?")
)
.child(
RadioGroup::horizontal("recommendation")
.children((0..=10).map(|i| i.to_string()))
.selected_index(self.recommendation)
.on_change(cx.listener(|view, index, _, cx| {
view.recommendation = Some(*index);
cx.notify();
}))
)
)
}
}#Payment Method Selection
struct PaymentView {
payment_method: Option<usize>,
}
impl Render for PaymentView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_4()
.child(
div()
.text_lg()
.font_semibold()
.child("Select Payment Method")
)
.child(
RadioGroup::vertical("payment")
.child(
Radio::new("credit-card")
.label("Credit Card")
.child(
div()
.text_color(cx.theme().muted_foreground)
.child("Visa, MasterCard, American Express")
)
)
.child(
Radio::new("paypal")
.label("PayPal")
.child(
div()
.text_color(cx.theme().muted_foreground)
.child("Pay with your PayPal account")
)
)
.child(
Radio::new("bank-transfer")
.label("Bank Transfer")
.child(
div()
.text_color(cx.theme().muted_foreground)
.child("Direct bank account transfer")
)
)
.selected_index(self.payment_method)
.on_change(cx.listener(|view, index, _, cx| {
view.payment_method = Some(*index);
cx.notify();
}))
)
}
}#Radio Group Usage
#Horizontal Layout
RadioGroup::horizontal("horizontal-group")
.children(["First", "Second", "Third"])
.selected_index(Some(0))
.on_change(cx.listener(|view, index, _, cx| {
println!("Selected index: {}", index);
cx.notify();
}))let horizontal = RadioGroup::horizontal()
.options(["First", "Second", "Third"])
.selected(app.horizontal)
.on_change(Message::Horizontal);#Vertical Layout
RadioGroup::vertical("vertical-group")
.child(Radio::new("option1").label("United States"))
.child(Radio::new("option2").label("Canada"))
.child(Radio::new("option3").label("Mexico"))
.selected_index(Some(1))
.disabled(false)description: "A vertical group with a single option.";
RadioGroup {
options: [
"Standard delivery — the only method for this address"
];
selected: 0;
}// The group with one option: a radio never toggles off, so the only choice
// is the selected one and the group cannot be emptied.
let vertical = RadioGroup::new()
.options(["Standard delivery — the only method for this address"])
.selected(app.vertical)
.on_change(Message::Vertical);#Styled Radio Group
RadioGroup::vertical("styled-group")
.w(px(220.))
.p_2()
.border_1()
.border_color(cx.theme().border)
.rounded(cx.theme().radius)
.child(Radio::new("option1").label("Option 1"))
.child(Radio::new("option2").label("Option 2"))
.child(Radio::new("option3").label("Option 3"))
.selected_index(Some(0))#Disabled Radio Group
RadioGroup::vertical("disabled-group")
.children(["Option A", "Option B", "Option C"])
.selected_index(Some(1))
.disabled(true) // Disables all radio buttons in the group// `disabled(true)` drops every message in the set, which is what makes the
// whole group inert without touching its selection.
let disabled_group = RadioGroup::new()
.options(["Option A", "Option B", "Option C"])
.selected(Some(1))
.disabled(true);#Best Practices
- Use RadioGroup: Always prefer
RadioGroupover individualRadiocomponents for mutually exclusive choices - Clear Labels: Provide descriptive labels that clearly indicate what each option represents
- Default Selection: Consider providing a sensible default selection, especially for required fields
- Logical Order: Arrange options in a logical order (alphabetical, frequency of use, or importance)
- Limit Options: Keep the number of radio options reasonable (typically 2-7 options)
- Group Related Options: Use visual grouping and clear headings for multiple radio groups
- Responsive Design: Consider using horizontal layout for fewer options and vertical for more options
#API Reference
#Radio
| Method | Description |
|---|---|
new(id) | Create a new radio button with the given ID |
label(text) | Set label text |
checked(bool) | Set checked state |
disabled(bool) | Set disabled state |
on_change(fn) | Requested checked value, receives &bool |
tab_stop(bool) | Enable/disable tab navigation (default: true) |
tab_index(isize) | Set tab order index (default: 0) |
#RadioGroup
| Method | Description |
|---|---|
new(id) | Create a vertical radio group with no selection |
horizontal(id) | Create a new horizontal radio group |
vertical(id) | Create a new vertical radio group |
layout(Axis) | Set layout direction (Vertical or Horizontal) |
child(Radio) | Add a single radio button to the group |
children(items) | Add multiple radio buttons from an iterator |
selected_index(Option<usize>) | Set the selected option by index |
disabled(bool) | Disable all radio buttons in the group |
on_change(fn) | Requested selected index, receives &usize |
#Styling
Both Radio and RadioGroup implement Styled trait for custom styling:
Radio also implements Sizable trait:
xsmall()- Extra small sizesmall()- Small sizemedium()- Medium size (default)large()- Large size
#Radio
One option in a mutually exclusive set. Mirrors GPUI Component's `Radio`: a label, sizes, and a disabled state. Pair with `RadioGroup` or bind `checked` yourself. `checked` is the owner's state. The radio reads it and reports clicks, so a host that binds it is responsible for writing it back.
| Name | Kind | Type | Default |
|---|---|---|---|
checked | in-out | bool | |
label | in | string | |
enabled | in | bool | true |
size | in | ControlSize | ControlSize.medium |
changed | callback | (checked: bool) |
#RadioGroup
A vertical stack of radios that keep a single selected index.
| Name | Kind | Type | Default |
|---|---|---|---|
options | in | [string] | |
selected | in-out | int | 0 |
enabled | in | bool | true |
size | in | ControlSize | ControlSize.medium |
changed | callback | (index: int) |