---
title: "ButtonGroup"
description: "A layout composable that places its children in a horizontal sequence. When a child uses
`ButtonGroupScope.animateWidth` 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."
type: "component"
---

<div class='type'>Composable Component</div>



A layout composable that places its children in a horizontal sequence. When a child uses
`ButtonGroupScope.animateWidth` 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.

<a id='references'></a>

<div class='sourceset sourceset-common'>Common</div>


> **Deprecated** Please use the overload with overflowIndicator parameter. This overload will create a composable that is cut off if there are too many items to fit on the screen neatly.

```kotlin
@Composable
@ExperimentalMaterial3ExpressiveApi
fun ButtonGroup(
    modifier: Modifier = Modifier,
    @FloatRange(0.0) expandedRatio: Float = ButtonGroupDefaults.ExpandedRatio,
    horizontalArrangement: Arrangement.Horizontal = ButtonGroupDefaults.HorizontalArrangement,
    content: @Composable ButtonGroupScope.() -> Unit,
)
```


#### Parameters

| | |
| --- | --- |
| modifier | the `Modifier` to be applied to the button group. |
| expandedRatio | 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. By Default, standard button group will expand the interacted child element by `ButtonGroupDefaults.ExpandedRatio` of its width and this will be propagated to its neighbors. If 0f is passed into this slot, then the interacted child element will not expand at all and the neighboring elements will not compress. If 1f is passed into this slot, then the interacted child element will expand to 200% of its default width when pressed. |
| 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`. |




<div class='sourceset sourceset-common'>Common</div>


> **Deprecated** Use overload with `verticalAlignment` parameter

```kotlin
@Composable
@ExperimentalMaterial3ExpressiveApi
fun ButtonGroup(
    overflowIndicator: @Composable (ButtonGroupMenuState) -> Unit,
    modifier: Modifier = Modifier,
    @FloatRange(0.0) expandedRatio: Float = ButtonGroupDefaults.ExpandedRatio,
    horizontalArrangement: Arrangement.Horizontal = ButtonGroupDefaults.HorizontalArrangement,
    content: ButtonGroupScope.() -> Unit,
)
```


#### Parameters

| | |
| --- | --- |
| overflowIndicator | composable that is displayed at the end of the button group if it needs to overflow. It receives a `ButtonGroupMenuState`. |
| modifier | the `Modifier` to be applied to the button group. |
| expandedRatio | 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. By Default, standard button group will expand the interacted child element by `ButtonGroupDefaults.ExpandedRatio` of its width and this will be propagated to its neighbors. If 0f is passed into this slot, then the interacted child element will not expand at all and the neighboring elements will not compress. If 1f is passed into this slot, then the interacted child element will expand to 200% of its default width when pressed. |
| horizontalArrangement | The horizontal arrangement of the button group's children. |
| content | the content displayed in the button group, expected to use a composable that i s tagged with `ButtonGroupScope.animateWidth`. |




<div class='sourceset sourceset-common'>Common</div>


```kotlin
@Composable
@ExperimentalMaterial3ExpressiveApi
fun ButtonGroup(
    overflowIndicator: @Composable (ButtonGroupMenuState) -> Unit,
    modifier: Modifier = Modifier,
    @FloatRange(0.0) expandedRatio: Float = ButtonGroupDefaults.ExpandedRatio,
    horizontalArrangement: Arrangement.Horizontal = ButtonGroupDefaults.HorizontalArrangement,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: ButtonGroupScope.() -> Unit,
)
```


#### Parameters

| | |
| --- | --- |
| overflowIndicator | composable that is displayed at the end of the button group if it needs to overflow. It receives a `ButtonGroupMenuState`. |
| modifier | the `Modifier` to be applied to the button group. |
| expandedRatio | 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. By Default, standard button group will expand the interacted child element by `ButtonGroupDefaults.ExpandedRatio` of its width and this will be propagated to its neighbors. If 0f is passed into this slot, then the interacted child element will not expand at all and the neighboring elements will not compress. If 1f is passed into this slot, then the interacted child element will expand to 200% of its default width when pressed. |
| horizontalArrangement | The horizontal arrangement of the button group's children. |
| verticalAlignment | The vertical alignment of the button group's children. |
| content | the content displayed in the button group, expected to use a composable that i s tagged with `ButtonGroupScope.animateWidth`. |






## Code Examples
### ButtonGroupSample
```kotlin
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun ButtonGroupSample() {
    val numButtons = 10
    ButtonGroup(
        overflowIndicator = { menuState ->
            ButtonGroupDefaults.OverflowIndicator(menuState = menuState)
        },
        verticalAlignment = Alignment.Top,
    ) {
        for (i in 0 until numButtons) {
            clickableItem(onClick = {}, label = "$i")
        }
    }
}
```
### MultiSelectConnectedButtonGroupSample
```kotlin
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MultiSelectConnectedButtonGroupSample() {
    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 checked = remember { mutableStateListOf(false, false, false) }
    Row(
        Modifier.padding(horizontal = 8.dp),
        horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
    ) {
        val modifiers = listOf(Modifier.weight(1f), Modifier.weight(1.5f), Modifier.weight(1f))
        options.forEachIndexed { index, label ->
            ToggleButton(
                checked = checked[index],
                onCheckedChange = { checked[index] = it },
                modifier = modifiers[index],
                shapes =
                    when (index) {
                        0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
                        options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
                        else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
                    },
            ) {
                Icon(
                    if (checked[index]) checkedIcons[index] else unCheckedIcons[index],
                    contentDescription = "Localized description",
                )
                Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
                Text(label)
            }
        }
    }
}
```
### MultiSelectConnectedButtonGroupWithFlowLayoutSample
```kotlin
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MultiSelectConnectedButtonGroupWithFlowLayoutSample() {
    val options = listOf("Work", "Restaurant", "Coffee", "Search", "Home")
    val unCheckedIcons =
        listOf(
            Icons.Outlined.Work,
            Icons.Outlined.Restaurant,
            Icons.Outlined.Coffee,
            Icons.Outlined.Search,
            Icons.Outlined.Home,
        )
    val checkedIcons =
        listOf(
            Icons.Filled.Work,
            Icons.Filled.Restaurant,
            Icons.Filled.Coffee,
            Icons.Filled.Search,
            Icons.Filled.Home,
        )
    val checked = remember { mutableStateListOf(false, false, false, false, false) }
    FlowRow(
        Modifier.padding(horizontal = 8.dp).fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
        verticalArrangement = Arrangement.spacedBy(2.dp),
    ) {
        options.forEachIndexed { index, label ->
            ToggleButton(
                checked = checked[index],
                onCheckedChange = { checked[index] = it },
                shapes =
                    when (index) {
                        0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
                        options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
                        else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
                    },
            ) {
                Icon(
                    if (checked[index]) checkedIcons[index] else unCheckedIcons[index],
                    contentDescription = "Localized description",
                )
                Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
                Text(label)
            }
        }
    }
}
```
### SingleSelectConnectedButtonGroupSample
```kotlin
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SingleSelectConnectedButtonGroupSample() {
    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)
    var selectedIndex by remember { mutableIntStateOf(0) }
    Row(
        Modifier.padding(horizontal = 8.dp),
        horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
    ) {
        val modifiers = listOf(Modifier.weight(1f), Modifier.weight(1.5f), Modifier.weight(1f))
        options.forEachIndexed { index, label ->
            ToggleButton(
                checked = selectedIndex == index,
                onCheckedChange = { selectedIndex = index },
                modifier = modifiers[index].semantics { role = Role.RadioButton },
                shapes =
                    when (index) {
                        0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
                        options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
                        else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
                    },
            ) {
                Icon(
                    if (selectedIndex == index) checkedIcons[index] else unCheckedIcons[index],
                    contentDescription = "Localized description",
                )
                Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
                Text(label)
            }
        }
    }
}
```
### SingleSelectConnectedButtonGroupWithFlowLayoutSample
```kotlin
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SingleSelectConnectedButtonGroupWithFlowLayoutSample() {
    val options = listOf("Work", "Restaurant", "Coffee", "Search", "Home")
    val unCheckedIcons =
        listOf(
            Icons.Outlined.Work,
            Icons.Outlined.Restaurant,
            Icons.Outlined.Coffee,
            Icons.Outlined.Search,
            Icons.Outlined.Home,
        )
    val checkedIcons =
        listOf(
            Icons.Filled.Work,
            Icons.Filled.Restaurant,
            Icons.Filled.Coffee,
            Icons.Filled.Search,
            Icons.Filled.Home,
        )
    var selectedIndex by remember { mutableIntStateOf(0) }
    FlowRow(
        Modifier.padding(horizontal = 8.dp).fillMaxWidth(),
        horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
        verticalArrangement = Arrangement.spacedBy(2.dp),
    ) {
        options.forEachIndexed { index, label ->
            ToggleButton(
                checked = selectedIndex == index,
                onCheckedChange = { selectedIndex = index },
                shapes =
                    when (index) {
                        0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
                        options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
                        else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
                    },
                modifier = Modifier.semantics { role = Role.RadioButton },
            ) {
                Icon(
                    if (selectedIndex == index) checkedIcons[index] else unCheckedIcons[index],
                    contentDescription = "Localized description",
                )
                Spacer(Modifier.size(ToggleButtonDefaults.IconSpacing))
                Text(label)
            }
        }
    }
}
```
### VerticalButtonGroupSample
```kotlin
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun VerticalButtonGroupSample() {
    val options = listOf("Button 1", "Button 2", "Button 3", "Button 4", "Button 5")
    var selectedIndex by remember { mutableIntStateOf(0) }
    Column(verticalArrangement = Arrangement.spacedBy((-6).dp)) {
        options.forEachIndexed { index, label ->
            val shape =
                when (index) {
                    0 ->
                        (ButtonGroupDefaults.connectedMiddleButtonShapes().shape
                                as RoundedCornerShape)
                            .copy(topStart = CornerSize(100), topEnd = CornerSize(100))
                    options.lastIndex ->
                        (ButtonGroupDefaults.connectedMiddleButtonShapes().shape
                                as RoundedCornerShape)
                            .copy(bottomStart = CornerSize(100), bottomEnd = CornerSize(100))
                    else -> ButtonGroupDefaults.connectedMiddleButtonShapes().shape
                }
            ToggleButton(
                checked = selectedIndex == index,
                onCheckedChange = { selectedIndex = index },
                shapes =
                    ToggleButtonDefaults.shapes(
                        shape = shape,
                        checkedShape = ButtonGroupDefaults.connectedButtonCheckedShape,
                    ),
                modifier = Modifier.semantics { role = Role.RadioButton },
            ) {
                Text(label)
            }
        }
    }
}
```

