Compose Unstyled 2.0 is out! Check the official announcement blog ->

Radio Group

A radio group component for single-choice selection.

import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.composeunstyled.RadioButton
import com.composeunstyled.SelectedIndicator
import com.composeunstyled.UnstyledRadioGroup

@Composable
fun RadioGroupDemo() {
  val values = listOf("Light", "Dark", "System")
  var selectedValue by remember { mutableStateOf("Light") }

  Column(
    modifier = Modifier
      .width(300.dp)
      .padding(16.dp),
    verticalArrangement = Arrangement.spacedBy(16.dp),
    horizontalAlignment = Alignment.CenterHorizontally,
  ) {
    UnstyledRadioGroup(
      value = selectedValue,
      onValueChange = { selectedValue = it },
      modifier = Modifier.fillMaxWidth(),
      accessibilityLabel = "Theme selection",
    ) {
      Column(
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.spacedBy(8.dp),
        modifier = Modifier.fillMaxWidth(),
      ) {
        values.forEach { value ->
          val selected = selectedValue == value
          val itemShape = RoundedCornerShape(14.dp)
          RadioButton(
            value = value,
            modifier = Modifier
              .fillMaxWidth()
              .clip(itemShape),
            indication = LocalIndication.current,
          ) {
            Row(
              modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 16.dp),
              verticalAlignment = Alignment.CenterVertically,
            ) {
              Box(
                modifier = Modifier
                  .size(20.dp)
                  .clip(CircleShape)
                  .background(
                    if (selected) Color.Black else Color(0xFFF8FAFC),
                  )
                  .border(1.dp, Color(0xFFCACACA), CircleShape),
                contentAlignment = Alignment.Center,
              ) {
                SelectedIndicator(
                  indication = LocalIndication.current,
                ) {
                  Box(
                    Modifier
                      .size(8.dp)
                      .clip(CircleShape)
                      .background(Color.White),
                  )
                }
              }
              Spacer(Modifier.width(16.dp))
              BasicText(value)
            }
          }
        }
      }
    }
  }
}

Features

  • Generic radio values
  • Arrow-key focus movement
  • Animated selected indicator

Installation

implementation("com.composables:composeunstyled-radio-group")

Anatomy

UnstyledRadioGroup(
  value = value,
  onValueChange = onValueChange,
) {
  RadioButton(value) {
    SelectedIndicator {
    }
  }
}

Concepts

  • UnstyledRadioGroup groups radio options for one selected value.
  • RadioButton renders an option inside the group.
  • SelectedIndicator renders only when its radio button is selected.

Accessibility

Use accessibilityLabel when the radio group does not contain a readable group label.

Code Examples

Selecting one radio option

Use the value parameter on each RadioButton to connect it to the group value:

var selected by remember { mutableStateOf("small") }

UnstyledRadioGroup(
  value = selected,
  onValueChange = { selected = it },
) {
  RadioButton("small") {
    BasicText("Small")
  }

  RadioButton("large") {
    BasicText("Large")
  }
}

Rendering the selected indicator

Use the SelectedIndicator component to render content only for the selected option:

RadioButton("large") {
  SelectedIndicator {
    BasicText("Selected")
  }

  BasicText("Large")
}

Animating the selected indicator

Use the enter and exit parameters on SelectedIndicator to animate the selected indicator:

RadioButton("large") {
  SelectedIndicator(
    enter = fadeIn(),
    exit = fadeOut(),
  ) {
    BasicText("Selected")
  }
}

API Reference

UnstyledRadioGroup

Parameter Type Description
value T? The value associated with the radio button.
onValueChange (T) -> Unit
modifier Modifier Modifier to be applied to the radio button.
accessibilityLabel String?
content RadioGroupScope.() -> Unit Composable function to define the content of the radio button.

RadioGroupScope.RadioButton

Parameter Type Description
value T The value associated with the radio button.
modifier Modifier Modifier to be applied to the radio button.
enabled Boolean Whether the radio button is enabled.
interactionSource MutableInteractionSource? Interaction source for the radio button.
indication Indication? Visual indication for interactions.
content RadioButtonScope.() -> Unit Composable function to define the content of the radio button.

RadioButtonScope.SelectedIndicator

Parameter Type Description
modifier Modifier Modifier to be applied to the radio button.
indication Indication? Visual indication for interactions.
enter EnterTransition
exit ExitTransition
content AnimatedVisibilityScope.() -> Unit Composable function to define the content of the radio button.