
CottonSheet is a zero-boilerplate, fully parameterized, and stackable ModalBottomSheet library for Compose Multiplatform. It allows you to manage bottom sheets globally via a simple controller API, keeping your UI logic clean and decoupled from state management.
CottonSheetHost once at the root; trigger sheets from anywhere.rememberModalBottomSheetState or visibility flags.CottonSheet is built on three core pillars:
The brain of the library. It maintains a mutableStateListOf of CottonSheetEntry objects. Every time you call show(), a new entry is pushed onto this stack. Each entry has a unique ID and its own set of CottonSheetParams.
The visual container. It should be placed at the root of your application. It observes the CottonSheetController's stack and renders each entry using the standard Material 3 ModalBottomSheet.
key(entry.id), ensuring that each has its own independent animation state and composition lifecycle.A comprehensive configuration object that lets you tweak everything from behavior (like isDismissable or isFullScreen) to aesthetics (like containerColor or customDragHandle).
Add the dependency to your commonMain source set in build.gradle.kts:
sourceSets {
commonMain.dependencies {
implementation("io.github.kratos1996:cottonsheet:1.0.1")
}
}Wrap your application's root content with CottonSheetHost. This provides the LocalCottonSheetController to all children.
@Composable
fun App() {
MaterialTheme {
CottonSheetHost {
// Your app content, e.g., Navigation Graph
MainScreen()
}
}
}Access the controller via LocalCottonSheetController.current.
val cottonSheet = LocalCottonSheetController.current
Button(onClick = {
cottonSheet.show { dismiss ->
Text("I am a CottonSheet!")
Button(onClick = dismiss) { Text("Close Me") }
}
}) {
Text("Open Sheet")
}Opening a sheet from within another sheet is seamless. The host handles the overlay automatically.
cottonSheet.show { dismiss1 ->
Column {
Text("Sheet 1")
Button(onClick = {
cottonSheet.show { dismiss2 ->
Text("Sheet 2 (On top of Sheet 1)")
Button(onClick = dismiss2) { Text("Close Sheet 2") }
Button(onClick = { cottonSheet.dismissAll() }) { Text("Close Everything") }
}
}) {
Text("Open Another Sheet")
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
skipPartiallyExpanded |
Boolean |
true |
If true, the sheet skips the half-expanded state. |
isFullScreen |
Boolean |
false |
If true, the sheet fills the entire screen height. |
isDismissable |
Boolean |
true |
Whether tapping outside or back press closes the sheet. |
showDragHandle |
Boolean |
true |
Whether to show the default M3 drag handle. |
customDragHandle |
Composable? |
null |
A custom Composable to replace the default handle. |
sheetMaxWidth |
Dp |
640.dp |
Maximum width (useful for tablets/desktop). |
containerColor |
Color? |
null |
Background color of the sheet. |
shape |
Shape? |
null |
Corner shape of the sheet. |
show(params, content): Pushes a new sheet onto the stack.dismiss(): Dismisses the top-most sheet.dismissAll(): Clears the entire stack immediately.| Platform | Support |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| Desktop (JVM) | ✅ |
| Web (Wasm/JS) | ✅ |
Copyright 2026 Ishant Sharma
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
CottonSheet is a zero-boilerplate, fully parameterized, and stackable ModalBottomSheet library for Compose Multiplatform. It allows you to manage bottom sheets globally via a simple controller API, keeping your UI logic clean and decoupled from state management.
CottonSheetHost once at the root; trigger sheets from anywhere.rememberModalBottomSheetState or visibility flags.CottonSheet is built on three core pillars:
The brain of the library. It maintains a mutableStateListOf of CottonSheetEntry objects. Every time you call show(), a new entry is pushed onto this stack. Each entry has a unique ID and its own set of CottonSheetParams.
The visual container. It should be placed at the root of your application. It observes the CottonSheetController's stack and renders each entry using the standard Material 3 ModalBottomSheet.
key(entry.id), ensuring that each has its own independent animation state and composition lifecycle.A comprehensive configuration object that lets you tweak everything from behavior (like isDismissable or isFullScreen) to aesthetics (like containerColor or customDragHandle).
Add the dependency to your commonMain source set in build.gradle.kts:
sourceSets {
commonMain.dependencies {
implementation("io.github.kratos1996:cottonsheet:1.0.1")
}
}Wrap your application's root content with CottonSheetHost. This provides the LocalCottonSheetController to all children.
@Composable
fun App() {
MaterialTheme {
CottonSheetHost {
// Your app content, e.g., Navigation Graph
MainScreen()
}
}
}Access the controller via LocalCottonSheetController.current.
val cottonSheet = LocalCottonSheetController.current
Button(onClick = {
cottonSheet.show { dismiss ->
Text("I am a CottonSheet!")
Button(onClick = dismiss) { Text("Close Me") }
}
}) {
Text("Open Sheet")
}Opening a sheet from within another sheet is seamless. The host handles the overlay automatically.
cottonSheet.show { dismiss1 ->
Column {
Text("Sheet 1")
Button(onClick = {
cottonSheet.show { dismiss2 ->
Text("Sheet 2 (On top of Sheet 1)")
Button(onClick = dismiss2) { Text("Close Sheet 2") }
Button(onClick = { cottonSheet.dismissAll() }) { Text("Close Everything") }
}
}) {
Text("Open Another Sheet")
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
skipPartiallyExpanded |
Boolean |
true |
If true, the sheet skips the half-expanded state. |
isFullScreen |
Boolean |
false |
If true, the sheet fills the entire screen height. |
isDismissable |
Boolean |
true |
Whether tapping outside or back press closes the sheet. |
showDragHandle |
Boolean |
true |
Whether to show the default M3 drag handle. |
customDragHandle |
Composable? |
null |
A custom Composable to replace the default handle. |
sheetMaxWidth |
Dp |
640.dp |
Maximum width (useful for tablets/desktop). |
containerColor |
Color? |
null |
Background color of the sheet. |
shape |
Shape? |
null |
Corner shape of the sheet. |
show(params, content): Pushes a new sheet onto the stack.dismiss(): Dismisses the top-most sheet.dismissAll(): Clears the entire stack immediately.| Platform | Support |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| Desktop (JVM) | ✅ |
| Web (Wasm/JS) | ✅ |
Copyright 2026 Ishant Sharma
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0