---
title: Bottom Sheet
description: A draggable bottom sheet with custom detents.
---

> **Recommendation:** For new interfaces, use [Drawer](https://composeunstyled.com/docs/components.mddrawer/) instead. It is a more flexible abstraction and the recommended API moving forward.



```kotlin expandable title="BottomSheetDemo.kt" githubUrl="https://github.com/composablehorizons/compose-unstyled/blob/main/demo/src/commonMain/kotlin/com/composeunstyled/demo/bottomsheet/BottomSheetDemo.kt"
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.composeunstyled.DragIndication
import com.composeunstyled.Sheet
import com.composeunstyled.SheetDetent
import com.composeunstyled.SheetDetent.Companion.FullyExpanded
import com.composeunstyled.SheetDetent.Companion.Hidden
import com.composeunstyled.Text
import com.composeunstyled.UnstyledBottomSheet
import com.composeunstyled.UnstyledButton
import com.composeunstyled.demo.borderToken
import com.composeunstyled.demo.colors
import com.composeunstyled.demo.surfaceToken
import com.composeunstyled.rememberBottomSheetState
import com.composeunstyled.theme.Theme

@Preview
@Composable
fun BottomSheetDemo() {
  val Peek = SheetDetent("peek") { containerHeight, _ ->
    containerHeight * 0.6f
  }
  val sheetState = rememberBottomSheetState(
    initialDetent = Peek,
    detents = listOf(Hidden, Peek, FullyExpanded),
  )
  Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center,
  ) {
    UnstyledButton(
      onClick = { sheetState.targetDetent = Peek },
      modifier = Modifier
        .heightIn(32.dp)
        .background(Theme[colors][surfaceToken])
        .border(1.dp, Theme[colors][borderToken]),
      contentPadding = PaddingValues(horizontal = 10.dp),
      indication = LocalIndication.current,
    ) {
      Text("Show bottom sheet")
    }

    UnstyledBottomSheet(
      state = sheetState,
      modifier = Modifier.fillMaxSize().padding(top = 12.dp),
    ) {
      Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter) {
        Sheet(
          modifier = Modifier
            .widthIn(max = 640.dp)
            .fillMaxWidth()
            .background(Theme[colors][surfaceToken])
            .border(1.dp, Theme[colors][borderToken]),
        ) {
          Box(Modifier.fillMaxWidth().height(1000.dp)) {
            DragIndication(
              modifier = Modifier
                .align(Alignment.TopCenter)
                .padding(top = 22.dp)
                .background(Theme[colors][borderToken])
                .size(32.dp, 4.dp),
              indication = LocalIndication.current,
            )
          }
        }
      }
    }
  }
}
```



## Features

- Custom detents
- Soft-keyboard support
- Dynamic content sizing
- Scrollable content without fixed height

## Installation

```kotlin
implementation("com.composables:composeunstyled-bottom-sheet:2.10.0")
```

## Anatomy

```kotlin
val sheetState = rememberBottomSheetState(
  initialDetent = SheetDetent.Hidden,
)

UnstyledBottomSheet(state = sheetState) {
  Sheet {
    DragIndication()
  }
}
```

## Concepts

- `SheetDetent` defines a height where the sheet can rest.
- `UnstyledBottomSheet` represents the draggable container the `Sheet` is dragged in.
- `Sheet` is the rendered bit of the sheet.
- `DragIndication` adds an interactive handle for expand, collapse, and dismiss actions.

## Accessibility

Use the `DragIndication` component when your sheet can move between multiple detents. It provides semantic expand,
collapse, and dismiss actions so users can control the sheet without dragging.

## Code Examples

### Showing and hiding the sheet

Use the `targetDetent` property to animate the sheet to a new detent:

```kotlin expandable
val sheetState = rememberBottomSheetState(
  initialDetent = SheetDetent.Hidden,
)

BasicText(
  text = "Show sheet",
  modifier = Modifier.clickable {
    sheetState.targetDetent = SheetDetent.FullyExpanded
  }
)

UnstyledBottomSheet(state = sheetState) {
  Sheet {
    BasicText(
      text = "Hide sheet",
      modifier = Modifier.clickable {
        sheetState.targetDetent = SheetDetent.Hidden
      }
    )
  }
}
```

### Waiting for the sheet animation

Use the suspend `animateTo()` function to wait until the sheet animation is done:

```kotlin expandable
val scope = rememberCoroutineScope()

BasicText(
  text = "Show sheet",
  modifier = Modifier.clickable {
    scope.launch {
      sheetState.animateTo(SheetDetent.FullyExpanded)
    }
  }
)
```

### Moving the sheet instantly

Use the `jumpTo()` function to move to a detent without animation:

```kotlin expandable
BasicText(
  text = "Open immediately",
  modifier = Modifier.clickable {
    sheetState.jumpTo(SheetDetent.FullyExpanded)
  }
)
```

### Creating sheets with custom detents

Use the `SheetDetent` constructor to create a new detent. Pass a unique identifier and a function
that calculates the detent height. The calculated height cannot be smaller than `0.dp`, taller than
the container, or taller than the sheet content.

Keep this calculation fast. It runs during sheet measurement.

Make sure to pass your new detent when creating your bottom sheet state:

```kotlin expandable
val Peek = SheetDetent("peek") { containerHeight, sheetHeight ->
  containerHeight * 0.6f
}

val sheetState = rememberBottomSheetState(
  initialDetent = Peek,
  detents = listOf(SheetDetent.Hidden, Peek, SheetDetent.FullyExpanded),
)

UnstyledBottomSheet(state = sheetState) {
  Sheet {
    DragIndication()
  }
}
```

### Updating detents after the state is created

Use the `invalidateDetents()` function to recalculate sheet detents. This is useful when a custom
detent reads a measured value that can change, such as a header height:

```kotlin expandable
val peekHeight = remember { mutableStateOf(96.dp) }
val Peek = remember {
  SheetDetent("peek") { _, _ -> peekHeight.value }
}

val sheetState = rememberBottomSheetState(
  initialDetent = Peek,
  detents = listOf(Peek, SheetDetent.FullyExpanded),
)

LaunchedEffect(peekHeight.value) {
  sheetState.invalidateDetents()
}
```

### Using scrollable sheet content

Use a scrollable layout inside the `Sheet` component to make content scroll within the current detent height:

### Working with the soft keyboard

Use the `offsetForIme` parameter to automatically move the sheet above the soft keyboard:

```kotlin expandable
var value by remember { mutableStateOf("") }

UnstyledBottomSheet(
  state = sheetState,
  offsetForIme = true,
) {
  Sheet {
    BasicTextField(
      value = value,
      onValueChange = { value = it },
    )
  }
}
```

### Customizing sheet animation between detents

Use the `animationSpec` parameter to customize the default animation between detents:

```kotlin expandable
val sheetState = rememberBottomSheetState(
  initialDetent = SheetDetent.Hidden,
  animationSpec = spring(
    dampingRatio = Spring.DampingRatioMediumBouncy,
    stiffness = Spring.StiffnessLow,
  ),
)
```

## API Reference

### rememberBottomSheetState

| Parameter | Type | Description |
|-----------|------|-------------|
| `initialDetent` | `SheetDetent` | A `SheetDetent` which controls the height in which the sheet will be introduced within its container. |
| `detents` | `List<SheetDetent>` |  |
| `animationSpec` | `AnimationSpec<Float>` | An `AnimationSpec` used when animating the sheet across the different *sheetDetents*. |
| `confirmDetentChange` | `(SheetDetent) -> Boolean` |  |
| `decayAnimationSpec` | `DecayAnimationSpec<Float>` |  |
| `velocityThreshold` | `() -> Dp` |  |
| `positionalThreshold` | `(totalDistance: Dp) -> Dp` |  |
### BottomSheetState

| Parameter | Type | Description |
|-----------|------|-------------|
| `confirmDetentChange` | `(SheetDetent) -> Boolean` |  |
| `detents` | `List<SheetDetent>` |  |
| `currentDetent` | `SheetDetent` | The `SheetDetent` in which the sheet is currently rested on. Setting a new detent will cause the sheet to animate to that detent. |
| `targetDetent` | `SheetDetent` | The `SheetDetent` in which the sheet is about to rest on, if it is being dragged or animated. |
| `isIdle` | `Boolean` | Whether the sheet is currently resting at a specific detent. |
| `offset` | `Float` | The current offset of the sheet. |
| `closestDetent` | `SheetDetent?` |  |
| `onIndicationClicked` | `() -> Unit` |  |
| `fun progress()` | `(SheetDetent, SheetDetent) -> Float` |  |
| `suspend fun animateTo()` | `suspend (SheetDetent, AnimationSpec<Float>?) -> Unit` | Animates the sheet to the given detent. This is a `suspend` function, which you can use to wait until the animation is complete. |
| `fun jumpTo()` | `(SheetDetent) -> Unit` | Makes the sheet to immediately appear to the given detent without any animation. |
| `fun invalidateDetents()` | `() -> Unit` |  |
| `fun UnstyledBottomSheet()` | `(BottomSheetState, Modifier, Boolean, Boolean, Boolean, BottomSheetScope.() -> Unit` |  |
### BottomSheet

| Parameter | Type | Description |
|-----------|------|-------------|
| `state` | `BottomSheetState` | The `BottomSheetState` for the component |
| `modifier` | `Modifier` | The `Modifier` for the component |
| `enabled` | `Boolean` | Enables or disables dragging. |
| `offsetForIme` | `Boolean` |  |
| `measureContentBeyondContainerBounds` | `Boolean` |  |
| `content` | `BottomSheetScope.() -> Unit` | The contents of the sheet. |
### BottomSheetScope.Sheet

| Parameter | Type | Description |
|-----------|------|-------------|
| `modifier` | `Modifier` | The `Modifier` for the component |
| `content` | `() -> Unit` | The contents of the sheet. |
### BottomSheetScope.DragIndication

| Parameter | Type | Description |
|-----------|------|-------------|
| `modifier` | `Modifier` | The `Modifier` for the component |
| `indication` | `Indication?` |  |
| `interactionSource` | `MutableInteractionSource?` |  |
