Skip to content

ButtonGroup and connected button groups in Compose

There are two kinds of button group.

Tonal rounded squares; the selected option becomes a primary pill and widens

Connected groups look like a segmented control. Build one from ToggleButtons with the connected shapes for the first, middle and last items:

@Composable
fun PeriodPicker(options: List<String>) {
var selected by rememberSaveable { mutableIntStateOf(0) }
// Connected button group = single-select segmented control with expressive shapes.
// Replaces SingleChoiceSegmentedButtonRow when you want the M3 Expressive look.
FlowRow(horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)) {
options.forEachIndexed { index, label ->
ToggleButton(
checked = selected == index,
onCheckedChange = { selected = index },
shapes = when (index) {
0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
},
modifier = Modifier.weight(1f).semantics { role = Role.RadioButton },
) { Text(label) }
}
}
}

In Google’s examples the selected segment turns dark, becomes a full pill and gets a little wider while the others shrink. The connected shapes give you the shape change. The width change comes from weight and the group’s layout.

Press and hold a button: it grows and its neighbours give way

ButtonGroup lays out actions where the pressed one grows and pushes its neighbours. Items that don’t fit move into an overflow menu automatically.

@Composable
fun EditorActions(onAction: (String) -> Unit) {
// Standard button group: neighbours squeeze when one is pressed; extra items go to overflow.
ButtonGroup(
overflowIndicator = { menuState -> ButtonGroupDefaults.OverflowIndicator(menuState = menuState) },
) {
listOf("Bold", "Italic", "Underline", "Strike", "Code", "Link").forEach { label ->
clickableItem(onClick = { onAction(label) }, label = label)
}
}
}

Inside ButtonGroup, add items with clickableItem, toggleableItem or customItem. ButtonGroupScope is not a RowScope, so plain composables don’t go there.

  • Single-select: put Role.RadioButton on each item (as in the snippet).
  • Multi-select: use Role.Checkbox.
  • Label every icon-only item.
  • Using the same ButtonGroupDefaults.connectedMiddleButtonShapes() for every item. The first and last items need their own shapes.
  • Expecting SingleChoiceSegmentedButtonRow to look expressive. It still works, but the connected group is the M3 Expressive look.

Related: toggle buttons · split button · design rules

Want your agent to get this right every time? Install the skill. If this page helped,star the repo on GitHub.

Built by Sanikant. Set in Google Sans Flex (SIL Open Font License), icons from Material Symbols (Apache 2.0). Material Design is a Google trademark; this project is not affiliated with Google.