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 { /* Top app bar content */ }
}
) {
// Screen content
}
You can use the bottomBar
slot and a BottomAppBar
:
Scaffold(
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// 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 */
}
}
) {
// 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 */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
If you’re using the Scaffold
composable’s bottomBar
slot, you can use the
isFloatingActionButtonDocked
parameter to overlap the FAB with the bottom app
bar:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar { /* Bottom app bar content */ }
}
) {
// Screen content
}
Figure 3. A Scaffold
using the floatingActionButton
slot and the
bottomBar
slot. The isFloatingActionButtonDocked
parameter is set to
false
(top) and true
(bottom).
BottomAppBar
supports FAB cutouts with the cutoutShape
parameter, which
accepts any Shape
. It’s a good idea to
provide the same Shape
used by the docked component. For example,
FloatingActionButton
uses MaterialTheme.shapes.small
with a 50 percent
corner size as the default value for its shape
parameter:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = { /* ... */ }) {
/* FAB content */
}
},
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar(
// Defaults to null, that is, No cutout
cutoutShape = MaterialTheme.shapes.small.copy(
CornerSize(percent = 50)
)
) {
/* Bottom app bar content */
}
}
) {
// Screen content
}
Figure 4. A Scaffold
with a BottomAppBar
and a docked
FloatingActionButton
. The BottomAppBar
has a custom cutoutShape
that
matches the Shape
used by the FloatingActionButton
.
Snackbars
Scaffold
provides a means to display snackbars.
This is provided via ScaffoldState
, which includes a
SnackbarHostState
property. You can use
rememberScaffoldState
to create an instance of ScaffoldState
that should be passed to Scaffold
with the scaffoldState
parameter. 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 scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
scaffoldState.snackbarHostState
.showSnackbar("Snackbar")
}
}
)
}
) {
// 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 scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Show snackbar") },
onClick = {
scope.launch {
val result = scaffoldState.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 */
}
}
}
}
)
}
) {
// Screen content
}
You can provide a custom Snackbar
with the snackbarHost
parameter. See the
SnackbarHost API reference docs
for
more information.
Drawers
Scaffold
provides a slot for a modal navigation drawer. The draggable sheet and
layout of the composable is handled internally.
You can use the drawerContent
slot, which uses a ColumnScope
to layout
drawer content composables in a column:
Scaffold(
drawerContent = {
Text("Drawer title", modifier = Modifier.padding(16.dp))
Divider()
// Drawer items
}
) {
// Screen content
}
Scaffold
accepts a number of additional drawer parameters. For example, you
can toggle whether or not the drawer responds to drags with the
drawerGesturesEnabled
parameter:
Scaffold(
drawerContent = {
// Drawer content
},
// Defaults to true
drawerGesturesEnabled = false
) {
// Screen content
}
Programmatically opening and closing the drawer is done via ScaffoldState
, which includes a
DrawerState
property that should
be passed to Scaffold
with the scaffoldState
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 scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
// Drawer content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Open or close drawer") },
onClick = {
scope.launch {
scaffoldState.drawerState.apply {
if (isClosed) open() else close()
}
}
}
)
}
) {
// Screen content
}
Modal drawers
If you want to implement a modal navigation drawer without a Scaffold
, you can
use the ModalDrawer
composable. It accepts similar drawer parameters to Scaffold
.
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
If you want to implement a bottom navigation drawer, you can use
the BottomDrawer
composable:
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
drawerState = drawerState,
drawerContent = {
// Drawer content
}
) {
// Screen content
}
Bottom sheets
If you want to implement a standard bottom sheet, you can
use the BottomSheetScaffold
composable. It accepts similar parameters to Scaffold
, such as topBar
,
floatingActionButton
, and snackbarHost
. It includes additional parameters
that provide a means to display bottom sheets.
You can use the sheetContent
slot, which uses a ColumnScope
to layout sheet content composables in a column:
BottomSheetScaffold(
sheetContent = {
// Sheet content
}
) {
// Screen content
}
BottomSheetScaffold
accepts a number of additional sheet parameters. For
example, you can set the peek height of the sheet with the sheetPeekHeight
parameter. You can also toggle whether or not the drawer responds to drags with
the sheetGesturesEnabled
parameter.
BottomSheetScaffold(
sheetContent = {
// Sheet content
},
// Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
sheetPeekHeight = 128.dp,
// Defaults to true
sheetGesturesEnabled = false
) {
// Screen content
}
Programmatically expanding and collapsing the sheet is done via
BottomSheetScaffoldState
, which
includes a BottomSheetState
property. You
can use rememberBottomSheetScaffoldState
to create an instance of BottomSheetScaffoldState
that should be passed to
BottomSheetScaffold
with the scaffoldState
parameter. BottomSheetState
provides access to the expand
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.
val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetContent = {
// Sheet content
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Expand or collapse sheet") },
onClick = {
scope.launch {
scaffoldState.bottomSheetState.apply {
if (isCollapsed) expand() else collapse()
}
}
}
)
}
) {
// Screen content
}
If you want to implement a modal bottom sheet, you can use the ModalBottomSheetLayout
composable:
val sheetState = rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
// Sheet content
}
) {
// Screen content
}
Backdrop
If you want to implement a backdrop, you can use the BackdropScaffold
composable.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)
BackdropScaffold
accepts a number of additional backdrop parameters. For
example, you can set the peek height of the back layer and the minimum inactive
height of the front layer with the peekHeight
and headerHeight
parameters.
You can also toggle whether or not the backdrop responds to drags with the
gesturesEnabled
parameter.
BackdropScaffold(
appBar = {
// Top app bar
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
},
// Defaults to BackdropScaffoldDefaults.PeekHeight
peekHeight = 40.dp,
// Defaults to BackdropScaffoldDefaults.HeaderHeight
headerHeight = 60.dp,
// Defaults to true
gesturesEnabled = false
)
Programmatically revealing and concealing the backdrop is done via
BackdropScaffoldState
. You can
use rememberBackdropScaffoldState
to create an instance of BackdropScaffoldState
that should be passed to
BackdropScaffold
with the scaffoldState
parameter. BackdropScaffoldState
provides access to the reveal
and
conceal
functions, as well as properties related to the current backdrop state. These
suspending functions require a CoroutineScope
— for example, using
rememberCoroutineScope
— and can be called in response to UI events.
val scaffoldState = rememberBackdropScaffoldState(
BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = {
TopAppBar(
title = { Text("Backdrop") },
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton(
onClick = {
scope.launch { scaffoldState.reveal() }
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu"
)
}
} else {
IconButton(
onClick = {
scope.launch { scaffoldState.conceal() }
}
) {
Icon(
Icons.Default.Close,
contentDescription = "Close"
)
}
}
},
elevation = 0.dp,
backgroundColor = Color.Transparent
)
},
backLayerContent = {
// Back layer content
},
frontLayerContent = {
// Front layer content
}
)