Skip to main content
The RadioButton component provides a circular selection indicator for choosing one option from a set of mutually exclusive options.

Import

import com.nomanr.lumo.ui.components.RadioButton
import com.nomanr.lumo.ui.components.RadioButtonColors

Component Signature

@Composable
fun RadioButton(
    modifier: Modifier = Modifier,
    selected: Boolean,
    onClick: (() -> Unit)? = null,
    enabled: Boolean = true,
    colors: RadioButtonColors = RadioButtonDefaults.colors(),
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable (() -> Unit)? = null,
)
Source: RadioButton.kt:35-110

Parameters

modifier
Modifier
default:"Modifier"
Modifier to be applied to the radio button
selected
Boolean
required
Whether this radio button is currently selected
onClick
(() -> Unit)?
default:"null"
Callback invoked when the radio button is clicked. If null, button becomes non-interactive
enabled
Boolean
default:"true"
Whether the radio button is enabled and can be selected
colors
RadioButtonColors
default:"RadioButtonDefaults.colors()"
Colors for different radio button states
interactionSource
MutableInteractionSource
Source of interactions for hover, press, and focus states
content
@Composable (() -> Unit)?
default:"null"
Optional label content displayed next to the radio button

Examples

Basic Radio Button

var selectedOption by remember { mutableStateOf("Option1") }

Column {
    Row(verticalAlignment = Alignment.CenterVertically) {
        RadioButton(
            selected = selectedOption == "Option1",
            onClick = { selectedOption = "Option1" }
        )
        Text("Option 1")
    }
    
    Row(verticalAlignment = Alignment.CenterVertically) {
        RadioButton(
            selected = selectedOption == "Option2",
            onClick = { selectedOption = "Option2" }
        )
        Text("Option 2")
    }
}

Radio Button with Integrated Label

The content parameter allows the label to be part of the clickable area:
var selectedOption by remember { mutableStateOf("A") }

Column {
    RadioButton(
        selected = selectedOption == "A",
        onClick = { selectedOption = "A" },
        content = { Text("Option A") }
    )
    
    RadioButton(
        selected = selectedOption == "B",
        onClick = { selectedOption = "B" },
        content = { Text("Option B") }
    )
}

Radio Button Group

data class RadioOption(val id: String, val label: String)

val options = listOf(
    RadioOption("small", "Small"),
    RadioOption("medium", "Medium"),
    RadioOption("large", "Large")
)

var selectedSize by remember { mutableStateOf(options[0].id) }

Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
    Text("Select Size:", style = AppTheme.typography.h4)
    
    options.forEach { option ->
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 4.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            RadioButton(
                selected = selectedSize == option.id,
                onClick = { selectedSize = option.id }
            )
            Text(option.label)
        }
    }
}

Disabled Radio Button

RadioButton(
    selected = true,
    onClick = null,
    enabled = false
)

RadioButton(
    selected = false,
    onClick = null,
    enabled = false
)

Custom Colors

var selected by remember { mutableStateOf(false) }

val customColors = RadioButtonColors(
    selectedColor = AppTheme.colors.error,
    unselectedColor = AppTheme.colors.error,
    disabledSelectedColor = AppTheme.colors.disabled,
    disabledUnselectedColor = AppTheme.colors.disabled
)

RadioButton(
    selected = selected,
    onClick = { selected = !selected },
    colors = customColors
)

RadioButtonColors

The RadioButtonColors data class allows customization (RadioButton.kt:132-154):
data class RadioButtonColors(
    val selectedColor: Color,
    val unselectedColor: Color,
    val disabledSelectedColor: Color,
    val disabledUnselectedColor: Color,
)

Styling

Default Styling

From RadioButtonDefaults (RadioButton.kt:112-119):
  • Radio Button Size: 20.dp
  • Unselected Stroke Width: 2.dp
  • Selected Stroke Width: 6.dp (creates filled appearance)
  • Minimum Interactive Size: 44.dp
  • Padding: 2.dp

Animation

  • Animation Duration: 100ms (RadioButton.kt:113)
  • Stroke width animates between selected and unselected states (RadioButton.kt:44-49)
  • Color transitions smoothly using animateColorAsState (RadioButton.kt:148-149)

Default Colors

From RadioButtonDefaults.colors() (RadioButton.kt:121-129):
  • Selected: Primary color
  • Unselected: Primary color
  • Disabled Selected: Disabled color
  • Disabled Unselected: Disabled color

Implementation Details

Visual Representation

The radio button is drawn as a circle with animated stroke width:
  • Unselected: Thin stroke (2.dp) creates a circle outline
  • Selected: Thick stroke (6.dp) creates a filled circle appearance
  • The stroke width animates smoothly between states (RadioButton.kt:44-49)

Click Behavior

The component uses two modifiers for interaction:
  1. selectable: Applied to the radio button itself for keyboard/accessibility (RadioButton.kt:53-64)
  2. clickable: Applied to the entire row when content is provided (RadioButton.kt:69-77)
This ensures the entire label is clickable when using the content parameter.

Accessibility

  • Minimum interactive size of 44.dp for touch targets (RadioButton.kt:62)
  • Automatically assigned Role.RadioButton semantic property (RadioButton.kt:57, 73)
  • Ripple effect on interaction with unbounded radius (RadioButton.kt:59-62)
  • Disabled state clearly indicated with reduced opacity colors
  • Mutually exclusive selection enforced through state management

Best Practices

Use Cases

  • Use radio buttons when users must select exactly one option from a set
  • For 2-5 options, display all radio buttons at once
  • For more than 5 options, consider using a dropdown menu instead
  • Always provide a default selected option

Radio Button vs Checkbox

  • Radio Button: One selection from multiple options (mutually exclusive)
  • Checkbox: Multiple independent selections

Grouping

// Ensure mutual exclusivity through shared state
var selectedOption by remember { mutableStateOf("option1") }

Column {
    listOf("option1", "option2", "option3").forEach { option ->
        RadioButton(
            selected = selectedOption == option,
            onClick = { selectedOption = option },
            content = { Text(option) }
        )
    }
}

Source Reference

Full implementation: com.nomanr.lumo.ui.components.RadioButton.kt

Build docs developers (and LLMs) love