Skip to main content
ModalBottomSheet displays a modal bottom sheet that slides up from the bottom of the screen. It includes a drag handle and supports gestures for dismissal.

Parameters

modifier
Modifier
default:"Modifier"
Modifier to be applied to the bottom sheet
sheetState
SheetState
default:"rememberModalBottomSheetState()"
State of the bottom sheet for controlling expansion
isVisible
Boolean
required
Whether the bottom sheet is currently visible
onDismissRequest
() -> Unit
required
Callback invoked when the user dismisses the sheet
sheetGesturesEnabled
Boolean
default:"true"
Whether gestures for dragging and dismissing are enabled
dragHandle
@Composable (() -> Unit)?
default:"BottomSheetDefaults.DragHandle()"
Optional drag handle composable. Pass null to hide the drag handle
content
@Composable ColumnScope.() -> Unit
required
Content to display inside the bottom sheet

Usage Examples

Basic Modal Bottom Sheet

var showBottomSheet by remember { mutableStateOf(false) }

Button(onClick = { showBottomSheet = true }) {
    Text("Show Bottom Sheet")
}

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false }
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(
            text = "Bottom Sheet Title",
            style = AppTheme.typography.h2
        )
        Spacer(modifier = Modifier.height(16.dp))
        Text("This is the content of the bottom sheet.")
        Spacer(modifier = Modifier.height(16.dp))
        Button(
            onClick = { showBottomSheet = false },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Close")
        }
    }
}

Bottom Sheet with List

var showBottomSheet by remember { mutableStateOf(false) }
val items = listOf("Option 1", "Option 2", "Option 3", "Option 4")

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false }
) {
    Column {
        Text(
            text = "Select an option",
            style = AppTheme.typography.h2,
            modifier = Modifier.padding(16.dp)
        )
        
        items.forEach { item ->
            Surface(
                onClick = {
                    // Handle selection
                    showBottomSheet = false
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(
                    text = item,
                    modifier = Modifier.padding(16.dp)
                )
            }
            HorizontalDivider()
        }
    }
}

Bottom Sheet with Controlled State

val sheetState = rememberModalBottomSheetState()
var isVisible by remember { mutableStateOf(false) }

ModalBottomSheet(
    sheetState = sheetState,
    isVisible = isVisible,
    onDismissRequest = { isVisible = false }
) {
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Controlled Bottom Sheet")
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { isVisible = false }) {
            Text("Close")
        }
    }
}

Bottom Sheet Without Drag Handle

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false },
    dragHandle = null
) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("No drag handle")
        Text("Swipe down or tap outside to dismiss")
    }
}

Bottom Sheet with Gestures Disabled

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false },
    sheetGesturesEnabled = false
) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Gestures disabled")
        Text("You must use the button to close")
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { showBottomSheet = false }) {
            Text("Close")
        }
    }
}

Bottom Sheet with Form

var showBottomSheet by remember { mutableStateOf(false) }
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false }
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        Text(
            text = "Contact Form",
            style = AppTheme.typography.h2
        )
        
        TextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") },
            modifier = Modifier.fillMaxWidth()
        )
        
        TextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("Email") },
            modifier = Modifier.fillMaxWidth()
        )
        
        Button(
            onClick = {
                // Submit form
                showBottomSheet = false
            },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Submit")
        }
    }
}

Bottom Sheet with Scrollable Content

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false }
) {
    Column {
        Text(
            text = "Terms and Conditions",
            style = AppTheme.typography.h2,
            modifier = Modifier.padding(16.dp)
        )
        
        LazyColumn(
            modifier = Modifier
                .weight(1f, fill = false)
                .padding(horizontal = 16.dp)
        ) {
            items(20) { index ->
                Text(
                    text = "Section ${index + 1}: Lorem ipsum dolor sit amet...",
                    modifier = Modifier.padding(vertical = 8.dp)
                )
            }
        }
        
        HorizontalDivider()
        
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(
                onClick = { showBottomSheet = false },
                modifier = Modifier.weight(1f)
            ) {
                Text("Decline")
            }
            Button(
                onClick = {
                    // Accept terms
                    showBottomSheet = false
                },
                modifier = Modifier.weight(1f)
            ) {
                Text("Accept")
            }
        }
    }
}

Bottom Sheet with Icons and Actions

data class ShareOption(
    val icon: ImageVector,
    val title: String,
    val onClick: () -> Unit
)

val shareOptions = listOf(
    ShareOption(Icons.Default.Email, "Email") { /* Share via email */ },
    ShareOption(Icons.Default.Message, "Message") { /* Share via message */ },
    ShareOption(Icons.Default.ContentCopy, "Copy Link") { /* Copy link */ }
)

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false }
) {
    Column {
        Text(
            text = "Share",
            style = AppTheme.typography.h2,
            modifier = Modifier.padding(16.dp)
        )
        
        shareOptions.forEach { option ->
            Surface(
                onClick = {
                    option.onClick()
                    showBottomSheet = false
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Row(
                    modifier = Modifier.padding(16.dp),
                    horizontalArrangement = Arrangement.spacedBy(16.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Icon(
                        imageVector = option.icon,
                        contentDescription = null
                    )
                    Text(option.title)
                }
            }
            HorizontalDivider()
        }
    }
}

Styling

Default Styles

The bottom sheet has these default styles:
  • Shape: Rounded top corners (16dp radius)
  • Container Color: AppTheme.colors.background
  • Scrim Color: AppTheme.colors.scrim
  • Drag Handle: 36dp wide, 6dp high, secondary color

Custom Drag Handle

ModalBottomSheet(
    isVisible = showBottomSheet,
    onDismissRequest = { showBottomSheet = false },
    dragHandle = {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            contentAlignment = Alignment.Center
        ) {
            Icon(
                imageVector = Icons.Default.DragHandle,
                contentDescription = "Drag handle"
            )
        }
    }
) {
    // Content
}

Features

  • Drag to Dismiss: Swipe down to dismiss the sheet
  • Scrim Click: Tap outside to dismiss
  • Animated Transitions: Smooth slide-up and slide-down animations
  • Drag Handle: Visual indicator for draggable area
  • Gesture Control: Enable/disable swipe gestures
  • State Management: Control expansion programmatically
  • Rounded Corners: Material design appearance
  • Theme Integration: Uses app theme colors

Best Practices

  1. Content Height: Keep content concise or make it scrollable
  2. Drag Handle: Keep the default drag handle for better UX
  3. Dismiss Options: Provide both gesture and button dismissal
  4. Focus Management: Focus important actions within the sheet
  5. Loading States: Show progress indicators for async operations
  6. Accessibility: Provide content descriptions for actions

Common Patterns

Confirmation Dialog

ModalBottomSheet(
    isVisible = showConfirmation,
    onDismissRequest = { showConfirmation = false }
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        Icon(
            imageVector = Icons.Default.Warning,
            contentDescription = null,
            tint = Color.Red,
            modifier = Modifier.size(48.dp)
        )
        Text(
            text = "Are you sure?",
            style = AppTheme.typography.h2
        )
        Text(
            text = "This action cannot be undone.",
            style = AppTheme.typography.body2
        )
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(
                onClick = { showConfirmation = false },
                modifier = Modifier.weight(1f)
            ) {
                Text("Cancel")
            }
            Button(
                onClick = {
                    // Perform action
                    showConfirmation = false
                },
                modifier = Modifier.weight(1f)
            ) {
                Text("Confirm")
            }
        }
    }
}

Source Reference

ModalBottomSheet component implementation: components-lab/src/commonMain/kotlin/com/nomanr/lumo/ui/components/ModalBottomSheet.kt

Build docs developers (and LLMs) love