BottomSheet in Jetpack Compose: Modal & Standard Sheet Guide
Jetpack Compose provides powerful APIs for creating bottom sheets that appear from the bottom of the screen. Whether you need a simple modal overlay or a resizable sheet with special behavior, Compose has you covered.
ModalBottomSheet: Simple Overlay
ModalBottomSheet is the simplest approach for displaying temporary content:
var showBottomSheet by remember { mutableStateOf(false) }
if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = { showBottomSheet = false },
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Choose an option", style = MaterialTheme.typography.headlineSmall)
Button(onClick = { showBottomSheet = false }) {
Text("Option 1")
}
}
}
}
Button(onClick = { showBottomSheet = true }) {
Text("Show Bottom Sheet")
}
BottomSheetScaffold: Resizable Sheet
For sheets that can be expanded and collapsed, use BottomSheetScaffold:
val sheetState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = sheetState,
sheetContent = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.imePadding()
) {
Text("Expandable Content")
TextField(
value = "",
onValueChange = {},
label = { Text("Enter text") },
modifier = Modifier.fillMaxWidth()
)
}
},
sheetPeekHeight = 56.dp
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Button(onClick = {
scope.launch {
sheetState.bottomSheetState.expand()
}
}) {
Text("Expand Sheet")
}
}
}
skipPartiallyExpanded: Control Snap Behavior
Control whether the sheet snaps to partial height or expands fully:
SheetState(
skipPartiallyExpanded = true,
density = LocalDensity.current
)
Programmatic Control: Hide & Expand
Use coroutines to control sheet state programmatically:
val scope = rememberCoroutineScope()
val sheetState = rememberBottomSheetScaffoldState()
Button(onClick = {
scope.launch {
if (sheetState.bottomSheetState.isVisible) {
sheetState.bottomSheetState.hide()
} else {
sheetState.bottomSheetState.expand()
}
}
}) {
Text("Toggle Sheet")
}
Forms Inside BottomSheet with imePadding
When using text inputs, add imePadding() to prevent keyboard overlap:
BottomSheetScaffold(
sheetContent = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.imePadding()
) {
var name by remember { mutableStateOf("") }
TextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { /* Handle submission */ },
modifier = Modifier.align(Alignment.End)
) {
Text("Submit")
}
}
}
)
Custom Snackbar Integration
Combine bottom sheets with Snackbars for feedback:
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }
) {
var showSheet by remember { mutableStateOf(false) }
if (showSheet) {
ModalBottomSheet(
onDismissRequest = { showSheet = false }
) {
Button(onClick = {
scope.launch {
snackbarHostState.showSnackbar("Action completed!")
showSheet = false
}
}) {
Text("Complete Action")
}
}
}
}
Best Practices
- Use
ModalBottomSheetfor simple dialogs and menus - Use
BottomSheetScaffoldwhen the sheet is part of the main layout - Always add
imePadding()when bottom sheets contain text inputs - Control sheet state with
rememberCoroutineScope()for smooth animations - Combine with
SnackbarHostfor user feedback
Bottom sheets are a versatile UI pattern that enhance user experience by keeping important actions accessible without full navigation.
8 Android App Templates → https://myougatheax.gumroad.com
Top comments (0)