Jetpack Compose offers an implementation of Material Design, a comprehensive design system
for creating digital interfaces. Material Components (buttons, cards, switches,
etc.) and layouts like Scaffold are available as composable functions.

Material Components are interactive building blocks for creating a user interface. Compose offers a number of these components out-of-the-box. To see which are available, check out the Compose Material API reference.
Material Components use values provided by a MaterialTheme in
your app:
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
To learn more about theming, check out the Design systems in Compose guides.
Content slots
Material Components that support inner content (text labels, icons, etc.) tend to offer “slots” — generic lambdas that accept composable content — as well as public constants, like size and padding, to support laying out inner content to match Material specifications.
An example of this is Button:
Button(
onClick = { /* ... */ },
// Uses ButtonDefaults.ContentPadding by default
contentPadding = PaddingValues(
start = 20.dp,
top = 12.dp,
end = 20.dp,
bottom = 12.dp
)
) {
// Inner content including an icon and a text label
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}

Figure 1. A Button using the content slot and default padding (left) and
a Button using the content slot that provides a custom contentPadding
(right).
Button has a generic content trailing lambda slot, which uses a RowScope to layout
content composables in a row. It also has a contentPadding parameter to apply
padding to the inner content. You can use constants provided through
ButtonDefaults, or custom values.
Another example is ExtendedFloatingActionButton:
ExtendedFloatingActionButton(
onClick = { /* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = { Text("Like") }
)

Figure 2. An ExtendedFloatingActionButton using the icon and text slots.
Instead of a generic content lambda, ExtendedFloatingActionButton has two
slots for an icon and a text label. While each slot supports generic
composable content, the component is opinionated about how these pieces of
inner content are laid out. It handles padding, alignment, and size
internally.
Scaffold
Compose provides convenient layouts for combining Material Components into
common screen patterns. Composables such as Scaffold provide
slots for various components and other screen elements.
Screen content
Scaffold has a generic content trailing lambda slot. The lambda receives an
instance of PaddingValues that
should be applied to the content root — for example, via Modifier.padding —
to offset the top and bottom bars, if they exist.
Scaffold(/* ... */) { contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}
App bars
Scaffold provides slots for a top app bar or a bottom app bar. The placement of the
composables is handled internally.

You can use the topBar slot and a TopAppBar:
Scaffold(
topBar = {
TopAppBar(title = {
Text("My App")
})
}
) { contentPadding ->
// Screen content
}

You can use the bottomBar slot and a BottomAppBar:
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) { contentPadding ->
// Screen content
}
These slots can be used for other Material Components like BottomNavigation.
You can also use custom composables — for an example, take a look at the
onboarding screen
from the Owl sample.
Floating action buttons

Scaffold provides a slot for a floating action button.
You can use the floatingActionButton slot and a FloatingActionButton:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
}
) { contentPadding ->
// Screen content
}
The bottom placement of the FAB composable is handled internally. You can use
the floatingActionButtonPosition parameter to adjust the horizontal
position:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
floatingActionButtonPosition = FabPosition.Center
) { contentPadding ->
// Screen content
}
Snackbars

Scaffold provides a means to display snackbars.
This is provided via SnackbarHost,
which includes a SnackbarHostState
property. SnackbarHostState provides access to the
showSnackbar
function. This suspending function requires a CoroutineScope — for example,
using
rememberCoroutineScope
— and can be called in response to UI events to show a
Snackbar
within Scaffold.
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
icon = { Icon(Icons.Filled.Image, contentDescription = "") },
onClick = {
scope.launch {
snackbarHostState.showSnackbar("Snackbar")
}
}
)
}
) { contentPadding ->
// Screen content
}
You can provide an optional action and adjust the duration of the Snackbar.
The snackbarHostState.showSnackbar function accepts additional actionLabel
and duration parameters, and returns a SnackbarResult.
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
icon = { Icon(Icons.Filled.Image, contentDescription = "") },
onClick = {
scope.launch {
val result = snackbarHostState
.showSnackbar(
message = "Snackbar",
actionLabel = "Action",
// Defaults to SnackbarDuration.Short
duration = SnackbarDuration.Indefinite
)
when (result) {
SnackbarResult.ActionPerformed -> {
/* Handle snackbar action performed */
}
SnackbarResult.Dismissed -> {
/* Handle snackbar dismissed */
}
}
}
}
)
}
) { contentPadding ->
// Screen content
}
You can provide a custom Snackbar with the snackbarHost parameter. See the
SnackbarHost API reference docs for
more information.
Drawers

ModalNavigationDrawer
is the Material Design navigation drawer.
You can use the drawerContent slot to provide a ModalDrawerSheet
and provide the drawer's contents:
ModalNavigationDrawer(
drawerContent = {
ModalDrawerSheet {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
NavigationDrawerItem(
label = { Text(text = "Drawer Item") },
selected = false,
onClick = { /*TODO*/ }
)
// ...other drawer items
}
}
) {
// Screen content
}
ModalNavigationDrawer accepts a number of additional drawer parameters. For example, you
can toggle whether or not the drawer responds to drags with the
gesturesEnabled parameter:
ModalNavigationDrawer(
drawerContent = {
ModalDrawerSheet {
// Drawer contents
}
},
gesturesEnabled = false
) {
// Screen content
}
Programmatically opening and closing the drawer is done via DrawerState. A DrawerState
should be passed to ModalNavigationDrawer with the drawerState parameter.
DrawerState provides access to the open and close functions, as
well as properties related to the current drawer state. These suspending
functions require a CoroutineScope — for example, using
rememberCoroutineScope
— and can be called in response to UI events.
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet { /* Drawer content */ }
},
) {
Scaffold(
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show drawer") },
icon = { Icon(Icons.Filled.Add, contentDescription = "") },
onClick = {
scope.launch {
drawerState.apply {
if (isClosed) open() else close()
}
}
}
)
}
) { contentPadding ->
// Screen content
}
}
Bottom sheets

If you want to implement a bottom sheet, you can
use the ModalBottomSheet
composable.
You can use the content slot, which uses a ColumnScope to layout
sheet content composables in a column:
ModalBottomSheet(onDismissRequest = { /* Executed when the sheet is dismissed */ }) {
// Sheet content
}
Programmatically expanding and collapsing the sheet is done via
SheetState. You can use
rememberSheetState
to create an instance of SheetState that should be passed to
ModalBottomSheet with the sheetState parameter. SheetState
provides access to the show and
collapse functions,
as well as properties related to the current sheet state. These
suspending functions require a CoroutineScope — for example, using
rememberCoroutineScope
— and can be called in response to UI events. Make sure to remove the
ModalBottomSheet from composition upon hiding the bottom sheet.
val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
var showBottomSheet by remember { mutableStateOf(false) }
Scaffold(
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show bottom sheet") },
icon = { Icon(Icons.Filled.Add, contentDescription = "") },
onClick = {
showBottomSheet = true
}
)
}
) { contentPadding ->
// Screen content
if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = {
showBottomSheet = false
},
sheetState = sheetState
) {
// Sheet content
Button(onClick = {
scope.launch { sheetState.hide() }.invokeOnCompletion {
if (!sheetState.isVisible) {
showBottomSheet = false
}
}
}) {
Text("Hide bottom sheet")
}
}
}
}
