ButtonGroup
Common
Component in Material 3 Compose
TODO link to mio page when available.
A layout composable that places its children in a horizontal sequence. When a child uses [Modifier.interactionSourceData] with a relevant [MutableInteractionSource], this button group can listen to the interactions and expand the width of the pressed child element as well as compress the neighboring child elements. Material3 components already use [Modifier.interactionSourceData] and will behave as expected.
TODO link to an image when available
@sample androidx.compose.material3.samples.ButtonGroupSample
A connected button group is a variant of a button group that have leading and trailing buttons that are asymmetric in shape and are used to make a selection.
Last updated:
Installation
dependencies {
implementation("androidx.compose.material3:material3:1.4.0-alpha02")
}
Overloads
@Composable
@ExperimentalMaterial3ExpressiveApi
fun ButtonGroup(
modifier: Modifier = Modifier,
@FloatRange(0.0) animateFraction: Float = ButtonGroupDefaults.animateFraction,
horizontalArrangement: Arrangement.Horizontal =
Arrangement.spacedBy(ButtonGroupDefaults.spaceBetween),
content: @Composable ButtonGroupScope.() -> Unit
)
Parameters
name | description |
---|---|
modifier | the [Modifier] to be applied to the button group. |
animateFraction | the percentage, represented by a float, of the width of the interacted child element that will be used to expand the interacted child element as well as compress the neighboring children. |
horizontalArrangement | The horizontal arrangement of the button group's children. |
content | the content displayed in the button group, expected to use a Material3 component or a composable that is tagged with [Modifier.interactionSourceData]. |
Code Examples
ButtonGroupSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun ButtonGroupSample() {
ButtonGroup {
val options = listOf("A", "B", "C", "D")
val checked = remember { mutableStateListOf(false, false, false, false) }
val modifiers =
listOf(
Modifier.weight(1.5f),
Modifier.weight(1f),
Modifier.width(90.dp),
Modifier.weight(1f)
)
options.fastForEachIndexed { index, label ->
ToggleButton(
checked = checked[index],
onCheckedChange = { checked[index] = it },
modifier = modifiers[index]
) {
Text(label)
}
}
}
}
MultiSelectConnectedButtonGroupSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MultiSelectConnectedButtonGroupSample() {
val startButtonShapes =
ButtonShapes(
shape = ButtonGroupDefaults.connectedLeadingButtonShape,
pressedShape = ButtonGroupDefaults.connectedLeadingButtonPressShape,
checkedShape = ToggleButtonDefaults.checkedShape
)
val middleButtonShapes =
ToggleButtonDefaults.shapes(
ShapeDefaults.Small,
ToggleButtonDefaults.pressedShape,
ToggleButtonDefaults.checkedShape
)
val endButtonShapes =
ButtonShapes(
shape = ButtonGroupDefaults.connectedTrailingButtonShape,
pressedShape = ButtonGroupDefaults.connectedTrailingButtonPressShape,
checkedShape = ToggleButtonDefaults.checkedShape
)
val options = listOf("Work", "Restaurant", "Coffee")
val unCheckedIcons =
listOf(Icons.Outlined.Work, Icons.Outlined.Restaurant, Icons.Outlined.Coffee)
val checkedIcons = listOf(Icons.Filled.Work, Icons.Filled.Restaurant, Icons.Filled.Coffee)
val shapes = listOf(startButtonShapes, middleButtonShapes, endButtonShapes)
val checked = remember { mutableStateListOf(false, false, false) }
ButtonGroup(
modifier = Modifier.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.connectedSpaceBetween),
animateFraction = 0f
) {
options.forEachIndexed { index, label ->
ToggleButton(
checked = checked[index],
onCheckedChange = { checked[index] = it },
shapes = shapes[index]
) {
Icon(
if (checked[index]) checkedIcons[index] else unCheckedIcons[index],
contentDescription = "Localized description"
)
Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
Text(label)
}
}
}
}
SingleSelectConnectedButtonGroupSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SingleSelectConnectedButtonGroupSample() {
val startButtonShapes =
ButtonShapes(
shape = ButtonGroupDefaults.connectedLeadingButtonShape,
pressedShape = ButtonGroupDefaults.connectedLeadingButtonPressShape,
checkedShape = ToggleButtonDefaults.checkedShape
)
val middleButtonShapes =
ToggleButtonDefaults.shapes(
ShapeDefaults.Small,
ToggleButtonDefaults.pressedShape,
ToggleButtonDefaults.checkedShape
)
val endButtonShapes =
ButtonShapes(
shape = ButtonGroupDefaults.connectedTrailingButtonShape,
pressedShape = ButtonGroupDefaults.connectedTrailingButtonPressShape,
checkedShape = ToggleButtonDefaults.checkedShape
)
val options = listOf("Work", "Restaurant", "Coffee")
val unCheckedIcons =
listOf(Icons.Outlined.Work, Icons.Outlined.Restaurant, Icons.Outlined.Coffee)
val checkedIcons = listOf(Icons.Filled.Work, Icons.Filled.Restaurant, Icons.Filled.Coffee)
val shapes = listOf(startButtonShapes, middleButtonShapes, endButtonShapes)
var selectedIndex by remember { mutableIntStateOf(0) }
ButtonGroup(
modifier = Modifier.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.connectedSpaceBetween),
animateFraction = 0f
) {
options.forEachIndexed { index, label ->
ToggleButton(
checked = selectedIndex == index,
onCheckedChange = { selectedIndex = index },
shapes = shapes[index]
) {
Icon(
if (selectedIndex == index) checkedIcons[index] else unCheckedIcons[index],
contentDescription = "Localized description"
)
Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
Text(label)
}
}
}
}