SplitButtonLayout
Component in Material 3 Compose
A [SplitButtonLayout] let user define a button group consisting of 2 buttons. The leading button performs a primary action, and the trailing button performs a secondary action that is contextually related to the primary action.
@sample androidx.compose.material3.samples.FilledSplitButtonSample @sample androidx.compose.material3.samples.TonalSplitButtonSample @sample androidx.compose.material3.samples.ElevatedSplitButtonSample @sample androidx.compose.material3.samples.OutlinedSplitButtonSample @sample androidx.compose.material3.samples.SplitButtonWithUnCheckableTrailingButtonSample @sample androidx.compose.material3.samples.SplitButtonWithTextSample @sample androidx.compose.material3.samples.SplitButtonWithIconSample
Choose the best split button for an action based on the amount of emphasis it needs. The more important an action is, the higher emphasis its button should be.
Use [SplitButtonDefaults.LeadingButton] and [SplitButtonDefaults.TrailingButton] to construct a
FilledSplitButton
. Filled split button is the high-emphasis version of split button. It should
be used for emphasizing important or final actions.
Use [SplitButtonDefaults.TonalLeadingButton] and [SplitButtonDefaults.TonalTrailingButton] to
construct a tonal SplitButton
. Tonal split button is the medium-emphasis version of split
buttons. It's a middle ground between filled SplitButton
and outlined SplitButton
Use [SplitButtonDefaults.ElevatedLeadingButton] and [SplitButtonDefaults.ElevatedTrailingButton]
to construct a elevated SplitButton
. Elevated split buttons are essentially tonal SplitButton
s with a shadow. To prevent shadow creep, only use them when absolutely necessary,
such as when the button requires visual separation from patterned container.
Use [SplitButtonDefaults.OutlinedLeadingButton] and [SplitButtonDefaults.OutlinedTrailingButton]
to construct a outlined SplitButton
. Outlined split buttons are medium-emphasis buttons. They
contain actions that are important, but are not the primary action in an app. Outlined buttons
pair well with filled SplitButton
s to indicate an alternative, secondary action.
Last updated:
Installation
dependencies {
implementation("androidx.compose.material3:material3:1.4.0-alpha02")
}
Overloads
@ExperimentalMaterial3ExpressiveApi
@Composable
fun SplitButtonLayout(
leadingButton: @Composable () -> Unit,
trailingButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
spacing: Dp = SplitButtonDefaults.Spacing,
)
Parameters
name | description |
---|---|
leadingButton | the leading button. You can specify your own composable or construct a [SplitButtonDefaults.LeadingButton] |
trailingButton | the trailing button.You can specify your own composable or construct a [SplitButtonDefaults.TrailingButton] |
modifier | the [Modifier] to be applied to this split button. |
spacing | The spacing between the [leadingButton] and [trailingButton] |
Code Examples
FilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun FilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Checked" else "Unchecked"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation"
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description"
)
}
}
)
}
TonalSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun TonalSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.TonalLeadingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TonalTrailingButton(
checked = checked,
onCheckedChange = { checked = it },
// modifier =
// Modifier.semantics {
// stateDescription = if (checked) "Checked" else "Unchecked"
// contentDescription = "Toggle Button"
// },
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation"
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description"
)
}
}
)
}
ElevatedSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun ElevatedSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.ElevatedLeadingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.ElevatedTrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Checked" else "Unchecked"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation"
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description"
)
}
}
)
}
OutlinedSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun OutlinedSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.OutlinedLeadingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.OutlinedTrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Checked" else "Unchecked"
contentDescription = "Toggle Button"
},
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation"
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description"
)
}
}
)
}
SplitButtonWithUnCheckableTrailingButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithUnCheckableTrailingButtonSample() {
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.LeadingIconSize),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.KeyboardArrowDown,
modifier = Modifier.size(SplitButtonDefaults.TrailingIconSize),
contentDescription = "Localized description"
)
}
}
)
}
SplitButtonWithTextSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithTextSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
) {
Text("My Button")
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it }
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation"
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description"
)
}
}
)
}
SplitButtonWithIconSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithIconSample() {
var checked by remember { mutableStateOf(false) }
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
) {
Icon(
Icons.Filled.Edit,
contentDescription = "Localized description",
Modifier.size(SplitButtonDefaults.LeadingIconSize)
)
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it }
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation"
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.TrailingIconSize).graphicsLayer {
this.rotationZ = rotation
},
contentDescription = "Localized description"
)
}
}
)
}