SplitButtonLayout
Composable Component
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.
Common
@ExperimentalMaterial3ExpressiveApi
@Composable
fun SplitButtonLayout(
leadingButton: @Composable () -> Unit,
trailingButton: @Composable () -> Unit,
modifier: Modifier = Modifier,
spacing: Dp = SplitButtonDefaults.Spacing,
)
Parameters
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) "Expanded" else "Collapsed"
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",
)
}
},
)
}
SplitButtonWithDropdownMenuSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun SplitButtonWithDropdownMenuSample() {
var checked by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxSize().wrapContentSize()) {
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) "Expanded" else "Collapsed"
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",
)
}
},
)
DropdownMenu(expanded = checked, onDismissRequest = { checked = false }) {
DropdownMenuItem(
text = { Text("Edit") },
onClick = { /* Handle edit! */ },
leadingIcon = { Icon(Icons.Outlined.Edit, contentDescription = null) },
)
DropdownMenuItem(
text = { Text("Settings") },
onClick = { /* Handle settings! */ },
leadingIcon = { Icon(Icons.Outlined.Settings, contentDescription = null) },
)
HorizontalDivider()
DropdownMenuItem(
text = { Text("Send Feedback") },
onClick = { /* Handle send feedback! */ },
leadingIcon = { Icon(Icons.Outlined.Email, contentDescription = null) },
trailingIcon = { Text("F11", textAlign = TextAlign.Center) },
)
}
}
}
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) "Expanded" else "Collapsed"
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) "Expanded" else "Collapsed"
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) "Expanded" else "Collapsed"
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 },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
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",
)
}
},
)
}
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 },
modifier =
Modifier.semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
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",
)
}
},
)
}
XSmallFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun XSmallFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.ExtraSmallContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("My Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
MediumFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun MediumFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.MediumContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("My Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
LargeFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun LargeFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.LargeContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("My Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
ExtraLargeFilledSplitButtonSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun ExtraLargeFilledSplitButtonSample() {
var checked by remember { mutableStateOf(false) }
val size = SplitButtonDefaults.ExtraLargeContainerHeight
SplitButtonLayout(
leadingButton = {
SplitButtonDefaults.LeadingButton(
onClick = { /* Do Nothing */ },
modifier = Modifier.heightIn(size),
shapes = SplitButtonDefaults.leadingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.leadingButtonContentPaddingFor(size),
) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.size(SplitButtonDefaults.leadingButtonIconSizeFor(size)),
contentDescription = "Localized description",
)
Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size)))
Text("Button", style = ButtonDefaults.textStyleFor(size))
}
},
trailingButton = {
SplitButtonDefaults.TrailingButton(
checked = checked,
onCheckedChange = { checked = it },
modifier =
Modifier.heightIn(size).semantics {
stateDescription = if (checked) "Expanded" else "Collapsed"
contentDescription = "Toggle Button"
},
shapes = SplitButtonDefaults.trailingButtonShapesFor(size),
contentPadding = SplitButtonDefaults.trailingButtonContentPaddingFor(size),
) {
val rotation: Float by
animateFloatAsState(
targetValue = if (checked) 180f else 0f,
label = "Trailing Icon Rotation",
)
Icon(
Icons.Filled.KeyboardArrowDown,
modifier =
Modifier.size(SplitButtonDefaults.trailingButtonIconSizeFor(size))
.graphicsLayer { this.rotationZ = rotation },
contentDescription = "Localized description",
)
}
},
)
}
Create your own Component Library
Material Components are meant to be used as is and they do not allow customizations. To build your own Jetpack Compose component library use Compose Unstyled