Scaffold

Functions summary

Unit
@Composable
Scaffold(
    modifier: Modifier,
    scaffoldState: ScaffoldState,
    topBar: @Composable () -> Unit,
    bottomBar: @Composable () -> Unit,
    snackbarHost: @Composable (SnackbarHostState) -> Unit,
    floatingActionButton: @Composable () -> Unit,
    floatingActionButtonPosition: FabPosition,
    isFloatingActionButtonDocked: Boolean,
    drawerContent: (@Composable ColumnScope.() -> Unit)?,
    drawerGesturesEnabled: Boolean,
    drawerShape: Shape,
    drawerElevation: Dp,
    drawerBackgroundColor: Color,
    drawerContentColor: Color,
    drawerScrimColor: Color,
    backgroundColor: Color,
    contentColor: Color,
    content: @Composable (PaddingValues) -> Unit
)

Material Design layout

Cmn
Unit
@Composable
Scaffold(
    contentWindowInsets: WindowInsets,
    modifier: Modifier,
    scaffoldState: ScaffoldState,
    topBar: @Composable () -> Unit,
    bottomBar: @Composable () -> Unit,
    snackbarHost: @Composable (SnackbarHostState) -> Unit,
    floatingActionButton: @Composable () -> Unit,
    floatingActionButtonPosition: FabPosition,
    isFloatingActionButtonDocked: Boolean,
    drawerContent: (@Composable ColumnScope.() -> Unit)?,
    drawerGesturesEnabled: Boolean,
    drawerShape: Shape,
    drawerElevation: Dp,
    drawerBackgroundColor: Color,
    drawerContentColor: Color,
    drawerScrimColor: Color,
    backgroundColor: Color,
    contentColor: Color,
    content: @Composable (PaddingValues) -> Unit
)

Material Design layout

Cmn

Functions

@Composable
fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: (@Composable ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
): Unit

Material Design layout

Scaffold implements the basic Material Design visual layout structure.

This component provides API to put together several Material components to construct your screen, by ensuring proper layout strategy for them and collecting necessary data so these components will work together correctly.

For similar components that implement different layout structures, see BackdropScaffold, which uses a backdrop as the centerpiece of the screen, and BottomSheetScaffold, which uses a persistent bottom sheet as the centerpiece of the screen.

Simple example of a Scaffold with TopAppBar, FloatingActionButton and drawer:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FabPosition
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldDefaults
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = { Text("Drawer content") },
    topBar = {
        TopAppBar(
            title = { Text("Simple Scaffold Screen") },
            navigationIcon = {
                IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) {
                    Icon(Icons.Filled.Menu, contentDescription = "Localized description")
                }
            },
        )
    },
    floatingActionButtonPosition = FabPosition.End,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Inc") },
            onClick = { /* fab click handler */ },
        )
    },
    contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
    content = { innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
            items(count = 100) {
                Box(Modifier.fillMaxWidth().height(50.dp).background(colors[it % colors.size]))
            }
        }
    },
)

More fancy usage with BottomAppBar with cutout and docked FloatingActionButton, which animates its shape when clicked:

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.BottomAppBar
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FabPosition
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldDefaults
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val scaffoldState = rememberScaffoldState()

// Consider negative values to mean 'cut corner' and positive values to mean 'round corner'
val sharpEdgePercent = -50f
val roundEdgePercent = 45f
// Start with sharp edges
val animatedProgress = remember { Animatable(sharpEdgePercent) }
// Create a coroutineScope for the animation
val coroutineScope = rememberCoroutineScope()
// animation value to animate shape
val progress = animatedProgress.value.roundToInt()

// When progress is 0, there is no modification to the edges so we are just drawing a rectangle.
// This allows for a smooth transition between cut corners and round corners.
val fabShape =
    if (progress < 0) {
        CutCornerShape(abs(progress))
    } else if (progress == roundEdgePercent.toInt()) {
        CircleShape
    } else {
        RoundedCornerShape(progress)
    }
// lambda to call to trigger shape animation
val changeShape: () -> Unit = {
    val target = animatedProgress.targetValue
    val nextTarget = if (target == roundEdgePercent) sharpEdgePercent else roundEdgePercent
    coroutineScope.launch {
        animatedProgress.animateTo(
            targetValue = nextTarget,
            animationSpec = TweenSpec(durationMillis = 600),
        )
    }
}

Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = { Text("Drawer content") },
    topBar = { TopAppBar(title = { Text("Scaffold with bottom cutout") }) },
    bottomBar = {
        BottomAppBar(cutoutShape = fabShape) {
            IconButton(
                onClick = { coroutineScope.launch { scaffoldState.drawerState.open() } }
            ) {
                Icon(Icons.Filled.Menu, contentDescription = "Localized description")
            }
        }
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Change shape") },
            onClick = changeShape,
            shape = fabShape,
        )
    },
    contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
    floatingActionButtonPosition = FabPosition.Center,
    isFloatingActionButtonDocked = true,
    content = { innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
            items(count = 100) {
                Box(Modifier.fillMaxWidth().height(50.dp).background(colors[it % colors.size]))
            }
        }
    },
)

To show a Snackbar, use SnackbarHostState.showSnackbar. Scaffold state already have ScaffoldState.snackbarHostState when created

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldDefaults
import androidx.compose.material.Snackbar
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        var clickCount by remember { mutableStateOf(0) }
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                // show snackbar as a suspend function
                scope.launch {
                    scaffoldState.snackbarHostState.showSnackbar("Snackbar # ${++clickCount}")
                }
            },
        )
    },
    contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
    content = { innerPadding ->
        Text(
            text = "Body content",
            modifier = Modifier.padding(innerPadding).fillMaxSize().wrapContentSize(),
        )
    },
)
Parameters
modifier: Modifier = Modifier

optional Modifier for the root of the Scaffold

scaffoldState: ScaffoldState = rememberScaffoldState()

state of this scaffold widget. It contains the state of the screen, e.g. variables to provide manual control over the drawer behavior, sizes of components, etc

topBar: @Composable () -> Unit = {}

top app bar of the screen. Consider using TopAppBar.

bottomBar: @Composable () -> Unit = {}

bottom bar of the screen. Consider using BottomAppBar.

snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }

component to host Snackbars that are pushed to be shown via SnackbarHostState.showSnackbar. Usually it's a SnackbarHost

floatingActionButton: @Composable () -> Unit = {}

Main action button of your screen. Consider using FloatingActionButton for this slot.

floatingActionButtonPosition: FabPosition = FabPosition.End

position of the FAB on the screen. See FabPosition for possible options available.

isFloatingActionButtonDocked: Boolean = false

whether floatingActionButton should overlap with bottomBar for half a height, if bottomBar exists. Ignored if there's no bottomBar or no floatingActionButton.

drawerContent: (@Composable ColumnScope.() -> Unit)? = null

content of the Drawer sheet that can be pulled from the left side (right for RTL).

drawerGesturesEnabled: Boolean = true

whether or not drawer (if set) can be interacted with via gestures

drawerShape: Shape = MaterialTheme.shapes.large

shape of the drawer sheet (if set)

drawerElevation: Dp = DrawerDefaults.Elevation

drawer sheet elevation. This controls the size of the shadow below the drawer sheet (if set)

drawerBackgroundColor: Color = MaterialTheme.colors.surface

background color to be used for the drawer sheet

drawerContentColor: Color = contentColorFor(drawerBackgroundColor)

color of the content to use inside the drawer sheet. Defaults to either the matching content color for drawerBackgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface.

drawerScrimColor: Color = DrawerDefaults.scrimColor

color of the scrim that obscures content when the drawer is open

backgroundColor: Color = MaterialTheme.colors.background

background of the scaffold body

contentColor: Color = contentColorFor(backgroundColor)

color of the content in scaffold body. Defaults to either the matching content color for backgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface.

content: @Composable (PaddingValues) -> Unit

content of your screen. The lambda receives an PaddingValues that should be applied to the content root via androidx.compose.foundation.layout.padding and androidx.compose.foundation.layout.consumeWindowInsets to properly offset top and bottom bars. If using androidx.compose.foundation.verticalScroll, apply this modifier to the child of the scroll, and not on the scroll itself.

@Composable
fun Scaffold(
    contentWindowInsets: WindowInsets,
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: (@Composable ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
): Unit

Material Design layout

Scaffold implements the basic Material Design visual layout structure.

This component provides API to put together several Material components to construct your screen, by ensuring proper layout strategy for them and collecting necessary data so these components will work together correctly.

For similar components that implement different layout structures, see BackdropScaffold, which uses a backdrop as the centerpiece of the screen, and BottomSheetScaffold, which uses a persistent bottom sheet as the centerpiece of the screen.

This particular overload provides ability to specify WindowInsets. Recommended value can be found in ScaffoldDefaults.contentWindowInsets.

Simple example of a Scaffold with TopAppBar, FloatingActionButton and drawer:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FabPosition
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldDefaults
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = { Text("Drawer content") },
    topBar = {
        TopAppBar(
            title = { Text("Simple Scaffold Screen") },
            navigationIcon = {
                IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) {
                    Icon(Icons.Filled.Menu, contentDescription = "Localized description")
                }
            },
        )
    },
    floatingActionButtonPosition = FabPosition.End,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Inc") },
            onClick = { /* fab click handler */ },
        )
    },
    contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
    content = { innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
            items(count = 100) {
                Box(Modifier.fillMaxWidth().height(50.dp).background(colors[it % colors.size]))
            }
        }
    },
)

More fancy usage with BottomAppBar with cutout and docked FloatingActionButton, which animates its shape when clicked:

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.TweenSpec
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.BottomAppBar
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FabPosition
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldDefaults
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

val scaffoldState = rememberScaffoldState()

// Consider negative values to mean 'cut corner' and positive values to mean 'round corner'
val sharpEdgePercent = -50f
val roundEdgePercent = 45f
// Start with sharp edges
val animatedProgress = remember { Animatable(sharpEdgePercent) }
// Create a coroutineScope for the animation
val coroutineScope = rememberCoroutineScope()
// animation value to animate shape
val progress = animatedProgress.value.roundToInt()

// When progress is 0, there is no modification to the edges so we are just drawing a rectangle.
// This allows for a smooth transition between cut corners and round corners.
val fabShape =
    if (progress < 0) {
        CutCornerShape(abs(progress))
    } else if (progress == roundEdgePercent.toInt()) {
        CircleShape
    } else {
        RoundedCornerShape(progress)
    }
// lambda to call to trigger shape animation
val changeShape: () -> Unit = {
    val target = animatedProgress.targetValue
    val nextTarget = if (target == roundEdgePercent) sharpEdgePercent else roundEdgePercent
    coroutineScope.launch {
        animatedProgress.animateTo(
            targetValue = nextTarget,
            animationSpec = TweenSpec(durationMillis = 600),
        )
    }
}

Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = { Text("Drawer content") },
    topBar = { TopAppBar(title = { Text("Scaffold with bottom cutout") }) },
    bottomBar = {
        BottomAppBar(cutoutShape = fabShape) {
            IconButton(
                onClick = { coroutineScope.launch { scaffoldState.drawerState.open() } }
            ) {
                Icon(Icons.Filled.Menu, contentDescription = "Localized description")
            }
        }
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Change shape") },
            onClick = changeShape,
            shape = fabShape,
        )
    },
    contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
    floatingActionButtonPosition = FabPosition.Center,
    isFloatingActionButtonDocked = true,
    content = { innerPadding ->
        LazyColumn(contentPadding = innerPadding) {
            items(count = 100) {
                Box(Modifier.fillMaxWidth().height(50.dp).background(colors[it % colors.size]))
            }
        }
    },
)

To show a Snackbar, use SnackbarHostState.showSnackbar. Scaffold state already have ScaffoldState.snackbarHostState when created.

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldDefaults
import androidx.compose.material.Snackbar
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        var clickCount by remember { mutableStateOf(0) }
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                // show snackbar as a suspend function
                scope.launch {
                    scaffoldState.snackbarHostState.showSnackbar("Snackbar # ${++clickCount}")
                }
            },
        )
    },
    contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
    content = { innerPadding ->
        Text(
            text = "Body content",
            modifier = Modifier.padding(innerPadding).fillMaxSize().wrapContentSize(),
        )
    },
)
Parameters
contentWindowInsets: WindowInsets

window insets to be passed to content slot via PaddingValues params. Scaffold will take the insets into account from the top/bottom only if the topBar/ bottomBar are not present, as the scaffold expect topBar/bottomBar to handle insets instead. Any insets consumed by other insets padding modifiers or consumeWindowInsets on a parent layout will be excluded from contentWindowInsets.

modifier: Modifier = Modifier

optional Modifier for the root of the Scaffold

scaffoldState: ScaffoldState = rememberScaffoldState()

state of this scaffold widget. It contains the state of the screen, e.g. variables to provide manual control over the drawer behavior, sizes of components, etc

topBar: @Composable () -> Unit = {}

top app bar of the screen. Consider using TopAppBar.

bottomBar: @Composable () -> Unit = {}

bottom bar of the screen. Consider using BottomAppBar.

snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }

component to host Snackbars that are pushed to be shown via SnackbarHostState.showSnackbar. Usually it's a SnackbarHost

floatingActionButton: @Composable () -> Unit = {}

Main action button of your screen. Consider using FloatingActionButton for this slot.

floatingActionButtonPosition: FabPosition = FabPosition.End

position of the FAB on the screen. See FabPosition for possible options available.

isFloatingActionButtonDocked: Boolean = false

whether floatingActionButton should overlap with bottomBar for half a height, if bottomBar exists. Ignored if there's no bottomBar or no floatingActionButton.

drawerContent: (@Composable ColumnScope.() -> Unit)? = null

content of the Drawer sheet that can be pulled from the left side (right for RTL).

drawerGesturesEnabled: Boolean = true

whether or not drawer (if set) can be interacted with via gestures

drawerShape: Shape = MaterialTheme.shapes.large

shape of the drawer sheet (if set)

drawerElevation: Dp = DrawerDefaults.Elevation

drawer sheet elevation. This controls the size of the shadow below the drawer sheet (if set)

drawerBackgroundColor: Color = MaterialTheme.colors.surface

background color to be used for the drawer sheet

drawerContentColor: Color = contentColorFor(drawerBackgroundColor)

color of the content to use inside the drawer sheet. Defaults to either the matching content color for drawerBackgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface.

drawerScrimColor: Color = DrawerDefaults.scrimColor

color of the scrim that obscures content when the drawer is open

backgroundColor: Color = MaterialTheme.colors.background

background of the scaffold body

contentColor: Color = contentColorFor(backgroundColor)

color of the content in scaffold body. Defaults to either the matching content color for backgroundColor, or, if it is not a color from the theme, this will keep the same value set above this Surface.

content: @Composable (PaddingValues) -> Unit

content of your screen. The lambda receives an PaddingValues that should be applied to the content root via androidx.compose.foundation.layout.padding and androidx.compose.foundation.layout.consumeWindowInsets to properly offset top and bottom bars. If using androidx.compose.foundation.verticalScroll, apply this modifier to the child of the scroll, and not on the scroll itself.