Hello everyone! Welcome to my blog! Today we will be talking about modal bottom sheets in android jetpack compose.
We will go through various steps, from implementing bottom sheets in your app to designing it to cater to a beautiful user experience.
You can take reference of the source code from the Github repository.
Here is how a modal bottom sheet enhances the user experience.
Dependency
implementation "androidx.compose.ui:ui:1.2.0-beta02"
implementation "androidx.compose.material:material:1.2.0-beta02"
1. Creating a Modal Bottom Sheet
For creating a Modal Bottom Sheet, let’s create our composable, SheetLayout().
@Composable
fun SheetLayout(){
ModalBottomSheetLayout(){
//Rest of the Scaffold
}
}
Note: Using the ModalBottomSheetLayout( ) directly will show an error. Thus add the @ExperimentalMaterialApi annotation above the @Composable annotation.
We will be using the ModalBottomSheetLayout( ) function which is provided to us by android itself. It takes two arguments
a. Initializing sheetState
b. Creating sheetContent
a. Initializing sheetState
sheetState is an important parameter since it determines the state of your modal bottom sheet, whether it will be shown on the screen or not.
@ExperimentalMaterialApi
@Composable
fun SheetLayout(){
bottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
ModalBottomSheetLayout(){
//Rest of the Scaffold
}
}
Two things happen here
- We save the state of the sheet in bottomSheetState using rememberModalBottomSheetState.
- The initialValue is set to Hidden, which means that the modal bottom sheet will not be visible as the app starts initially.
Note: You can also change the initial value of the bottomSheetState to ModalBottomSheetStateValue.Expanded or even HalfExpanded to show your bottom sheet initially.
b. Creating sheetContent
We can create a different composable to hold the content of our bottom sheet, i.e. the content that will be displayed in our bottom sheet.
@Composable
fun BottomSheetContent( ){
Surface(
modifier = Modifier.height(300.dp),
color = Color(0xff7353ba)
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "Modal Bottom Sheet",
fontSize = 20.sp,
modifier = Modifier.padding(10.dp),
color = Color.White)
Divider(
modifier = Modifier.padding(5.dp),
color = Color.White)
Text(
text = stringResource(R.string.greeting),
fontSize = 15.sp,
fontStyle = FontStyle.Italic,
color = Color.White,
modifier = Modifier.padding(10.dp))
}
}
}
Our composable BottomSheetContent contains the content of our bottom sheet. The following code looks like this:

Finally our SheetLayout( ) looks like this:
@Composable
fun BottomSheetContent() {
Surface(
modifier = Modifier.height(300.dp),
color = Color(0xff7353ba)
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Modal Bottom Sheet",
fontSize = 20.sp,
modifier = Modifier.padding(10.dp),
color = Color.White
)
Divider(
modifier = Modifier.padding(5.dp),
color = Color.White
)
Text(
text = stringResource(R.string.greeting),
fontSize = 15.sp,
fontStyle = FontStyle.Italic,
color = Color.White,
modifier = Modifier.padding(10.dp)
)
}
}
}
c. Toggling the Modal Sheet
We have added the content for our modal sheet, but we need to toggle the modal bottom sheet. We will be adding an anchor in the next step, for now, let’s add our flag variable to toggle the modal bottom sheet.
@ExperimentalMaterialApi
@Composable
fun SheetLayout() {
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
//Our flag variable
val showModalSheet = rememberSaveable {
mutableStateOf(false)
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
BottomSheetContent()
}) {
//Rest of the Scaffold
}
}
Here, showModalSheet is our flag variable which toggles between true and false as we toggle our bottom sheet.
2. Adding an anchor
In this step, we will be adding an anchor to trigger our Modal Bottom Sheet. We will use an icon as our anchor. Thus, creating another composable ModalSheetWithAnchor( ) looks like this:
@Composable
fun ModalSheetWithAnchor(
sheetState: ModalBottomSheetState,
showModalSheet: MutableState<Boolean>
) {
val scope = rememberCoroutineScope()
Box(modifier = Modifier.fillMaxSize()) {
Icon(
imageVector = Icons.Default.KeyboardArrowUp,
contentDescription = "",
modifier = Modifier.align(
alignment = Alignment.BottomCenter
)
)
}
}
Here:
- We pass sheetState which stores the state of the sheet and our flag variable showModalSheet which toggles the bottom sheet.
- We declare the scope variable which is used to launch the bottom sheet.
- We have added an icon KeyboardArrowUp which will act as an anchor for our bottom sheet and toggle it.
a. Triggering the Modal Bottom Sheet
Since the anchor that we have added should trigger the modal bottom sheet as it is clicked on, we can add this functionality using our friend, the Modifier!
@Composable
fun ModalSheetWithAnchor(
sheetState: ModalBottomSheetState,
showModalSheet: MutableState<Boolean>
) {
val scope = rememberCoroutineScope()
Box(modifier = Modifier.fillMaxSize()) {
Icon(
imageVector = Icons.Default.KeyboardArrowUp,
contentDescription = "",
modifier = Modifier
.align(alignment = Alignment.BottomCenter)
.clickable {
showModalSheet.value = !showModalSheet.value
scope.launch {
sheetState.show()
}
}
)
}
}
Here
- showModalSheet value, which is initially false, is toggled as we click on the icon.
- As the showModalSheet value toggles to true, we change the sheetState to show( ), using our scope declared earlier.
Now, adding this ModalSheetWithAnchor( ) composable to our SheetLayout( ), with the required arguments, our composable looks like this:
@ExperimentalMaterialApi
@Composable
fun SheetLayout() {
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
)
val showModalSheet = rememberSaveable {
mutableStateOf(false)
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = { BottomSheetContent() }
) {
ModalSheetWithAnchor(sheetState, showModalSheet)
}
}
Our SheetLayout( ) now runs like this:
b. Alternate way with BottomSheetScaffold
You can also achieve the same bottom sheet with an anchor, in an alternate way by using the BottomSheetScaffold( ).
We create here our composable BottomSheetWithAnchor( )
@Composable
fun BottomSheetWithAnchor() {
val sheetState = rememberBottomSheetState(initialValue = BottomSheetValue.Collapsed)
val scope = rememberCoroutineScope()
val sheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = sheetState
)
BottomSheetScaffold(
scaffoldState = sheetScaffoldState,
sheetElevation = 0.dp,
sheetBackgroundColor = Color.Transparent,
sheetPeekHeight = 49.dp,
sheetContent = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
IconButton(onClick = {
scope.launch {
if (sheetState.isCollapsed) {
sheetState.expand()
} else if (sheetState.isExpanded) {
sheetState.collapse()
}
}
}) {
val icon = if (sheetState.isExpanded) {
Icons.Filled.KeyboardArrowDown
} else {
Icons.Filled.KeyboardArrowUp
}
Icon(
imageVector = icon,
contentDescription = "Icon button"
)
}
BottomSheetContent()
}
}
)
}
Here
- We use the same variables, scope, and sheetState, which have the same functions as discussed in the previous steps.
- We also have a sheetScaffoldState which stores the scaffoldState and is required by our BottomSheetScaffold().
It is an inbuilt function provided by compose. It allows you to implement a bottom sheet along with your scaffold. Its arguments are discussed below
- scaffoldState: It is the state of the scaffold
- sheetElevation: It allows you to change the elevation of the bottom sheet, as you like.
- sheetBackgroundColor: Background color of the bottom sheet.
- sheetPeekHeight: It is the height of the bottom sheet when it is collapsed. In simple words, the part of the bottom sheet from the top that is visible even when the bottom sheet is collapsed.
- sheetContent: The content which is displayed in the sheet.
c. Adding an anchor:
Wrapped inside our column, lies the centrally aligned icon button, which acts as an anchor, and is used to toggle the bottom sheet, the same way that we have discussed above.
But, the anchor is customized to toggle the icon from pointing upwards to pointing downwards according to the state of the sheet.
val icon = if (sheetState.isExpanded) {
Icons.Filled.KeyboardArrowDown
} else {
Icons.Filled.KeyboardArrowUp
}
Icon(
imageVector = icon,
contentDescription = "Icon button"
)
Now, the code works pretty much the same way with an added customization.
3. Common Pitfall
While coding the BottomSheetContent, if in any case, you do not add anything, yet just a Single Column, then your code might not work.
@Composable
fun BottomSheetContent(){
Column {
//Your code or any layout that you have developed.
}
}
It would not work as we wish it to, but return an error
-E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.developersbreach, PID: 3926
java.lang.IllegalArgumentException: The initial value must have an associated anchor.
This occurs since there is no particular height or size associated with the content that we wish to display. Thus, in such cases, wrap it with another layout where you can specify the height.
Thus, here
@Composable
fun BottomSheetContent(){
Surface(
modifier = Modifier.height(300.dp),
) {
Column{
//Now you can add your content without any issue.
}
}
}
Here, we wrap our column with a surface that has a fixed height of 300.dp, thus the error is resolved.
4. Styling your bottom sheet
a. Customizing the values
Styling a modal bottom sheet basically deals with customizing the values associated with the ModalBottomSheetLayout, which include:
- sheetContent – The content of the bottom sheet.
- modifier – Optional Modifier for the entire component.
- sheetState – The state of the bottom sheet.
- sheetShape – The shape of the bottom sheet.
- sheetElevation – The elevation of the bottom sheet.
- scrimColor – The color of the scrim that is applied to the rest of the screen when the bottom sheet is visible. If the color passed is Color. Unspecified, then a scrim will no longer be applied and the bottom sheet will not block interaction with the rest of the screen when visible.
- content – The content of the rest of the screen.
We have already added the sheet content in our previous composable BottomSheetContent( ). Now let’s style our modal bottom sheet by playing with these values.
ModalBottomSheetLayout(
sheetState = sheetState,
sheetElevation = 50.dp,
sheetShape = RoundedCornerShape(
topStart = 50.dp,
topEnd = 50.dp),
scrimColor = Color(0xfffceff9),
sheetContent = { BottomSheetContent() }
) {
ModalSheetWithAnchor(sheetState, showModalSheet)
}
Now the modal bottom sheet now loooks like:

b. Animating the sheet
We can also add an animation to our modal bottom sheet, using the animation spec property in our rememberModalBottomSheetState( ).
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy
)
)
Spring animation is used here, as can be seen from the code above. You can play around with animation spec to add other animations to your modal bottom sheet as you like!
5. Project code and resources
- Android Jetpack Compose – Android Documentation
- Material Components in Android
- Github Repository compose actors
Here We Go Again : (
if (article == helpful) {
println("Like and subscribe to blog newsletter.")
} else {
println("Let me know what i should blog on.")
}
You must be logged in to post a comment.