# Connected button group

> Toggle buttons that share one row and morph their shape on press and selection.

- Category: Actions
- API status: Expressive
- Compose APIs: `ToggleButton`, `ButtonGroupDefaults.connectedLeadingButtonShapes`, `ButtonGroupDefaults.ConnectedSpaceBetween`
- Interactive page: https://m3e.ljzapps.app/#components/connected-button-group

## When to use

Use a connected group for a small set of related options where one or more can be active. The outer ends are fully round, the inner corners are small, and the selected button turns fully round.

Build it from a Row of ToggleButtons. Pick the leading, middle or trailing shapes from ButtonGroupDefaults by position, and use ConnectedSpaceBetween for the gap.

## Examples

### Single selection

Exactly one option is active at a time.

```kotlin
val options = listOf("Day", "Week", "Month")
var selected by rememberSaveable { mutableIntStateOf(0) }

Row(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()
            },
        ) {
            Text(label)
        }
    }
}
```

### Multiple selection

Any number of options can be active.

```kotlin
val options = listOf("Bold", "Italic", "Underline")
val selected = remember { mutableStateOf(setOf(0)) }

Row(horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)) {
    options.forEachIndexed { index, label ->
        ToggleButton(
            checked = index in selected.value,
            onCheckedChange = { checked ->
                selected.value = if (checked) selected.value + index else selected.value - index
            },
            shapes = when (index) {
                0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
                options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
                else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
            },
        ) {
            Text(label)
        }
    }
}
```

### Icons only

Icon labels need a content description.

```kotlin
val options = listOf(Icons.Rounded.ViewDay to "Day view", Icons.Rounded.ViewWeek to "Week view")
var selected by rememberSaveable { mutableIntStateOf(0) }

Row(horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)) {
    options.forEachIndexed { index, (icon, description) ->
        ToggleButton(
            checked = selected == index,
            onCheckedChange = { selected = index },
            shapes = if (index == 0) {
                ButtonGroupDefaults.connectedLeadingButtonShapes()
            } else {
                ButtonGroupDefaults.connectedTrailingButtonShapes()
            },
        ) {
            Icon(icon, contentDescription = description, modifier = Modifier.size(20.dp))
        }
    }
}
```

## Guidelines

Do:
- Keep labels short and parallel.
- Use three to five options.

Don't:
- Do not use a connected group for navigation.
- Do not wrap options onto a second row.

## Accessibility

- Each ToggleButton exposes its checked state. Group related buttons with a visible label nearby.
- Icon-only options must have a contentDescription.

## Related components

- [Button group](https://m3e.ljzapps.app/docs/components/button-group.md)
- [Toggle button](https://m3e.ljzapps.app/docs/components/toggle-button.md)
- [Segmented button](https://m3e.ljzapps.app/docs/components/segmented-button.md)
