We just launched Compose Examples featuring over 150+ components! Check it out →

Scaffold

Common

Component in Material Compose

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:

@sample androidx.compose.material.samples.SimpleScaffoldWithTopBar

More fancy usage with [BottomAppBar] with cutout and docked [FloatingActionButton], which animates it's shape when clicked:

@sample androidx.compose.material.samples.ScaffoldWithBottomBarAndCutout

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

Last updated:

Installation

dependencies {
   implementation("androidx.compose.material:material:1.8.0-alpha04")
}

Overloads

@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
)

Parameters

namedescription
contentWindowInsetswindow 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].
modifieroptional Modifier for the root of the [Scaffold]
scaffoldStatestate 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
topBartop app bar of the screen. Consider using [TopAppBar].
bottomBarbottom bar of the screen. Consider using [BottomAppBar].
snackbarHostcomponent to host [Snackbar]s that are pushed to be shown via [SnackbarHostState.showSnackbar]. Usually it's a [SnackbarHost]
floatingActionButtonMain action button of your screen. Consider using [FloatingActionButton] for this slot.
floatingActionButtonPositionposition of the FAB on the screen. See [FabPosition] for possible options available.
isFloatingActionButtonDockedwhether [floatingActionButton] should overlap with [bottomBar] for half a height, if [bottomBar] exists. Ignored if there's no [bottomBar] or no [floatingActionButton].
drawerContentcontent of the Drawer sheet that can be pulled from the left side (right for RTL).
drawerGesturesEnabledwhether or not drawer (if set) can be interacted with via gestures
drawerShapeshape of the drawer sheet (if set)
drawerElevationdrawer sheet elevation. This controls the size of the shadow below the drawer sheet (if set)
drawerBackgroundColorbackground color to be used for the drawer sheet
drawerContentColorcolor 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.
drawerScrimColorcolor of the scrim that obscures content when the drawer is open
backgroundColorbackground of the scaffold body
contentColorcolor 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.
contentcontent of your screen. The lambda receives an [PaddingValues] that should be applied to the content root via Modifier.padding to properly offset top and bottom bars. If you're using VerticalScroller, apply this modifier to the child of the scroller, and not on the scroller itself.
@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
)

Parameters

namedescription
modifieroptional Modifier for the root of the [Scaffold]
scaffoldStatestate 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
topBartop app bar of the screen. Consider using [TopAppBar].
bottomBarbottom bar of the screen. Consider using [BottomAppBar].
snackbarHostcomponent to host [Snackbar]s that are pushed to be shown via [SnackbarHostState.showSnackbar]. Usually it's a [SnackbarHost]
floatingActionButtonMain action button of your screen. Consider using [FloatingActionButton] for this slot.
floatingActionButtonPositionposition of the FAB on the screen. See [FabPosition] for possible options available.
isFloatingActionButtonDockedwhether [floatingActionButton] should overlap with [bottomBar] for half a height, if [bottomBar] exists. Ignored if there's no [bottomBar] or no [floatingActionButton].
drawerContentcontent of the Drawer sheet that can be pulled from the left side (right for RTL).
drawerGesturesEnabledwhether or not drawer (if set) can be interacted with via gestures
drawerShapeshape of the drawer sheet (if set)
drawerElevationdrawer sheet elevation. This controls the size of the shadow below the drawer sheet (if set)
drawerBackgroundColorbackground color to be used for the drawer sheet
drawerContentColorcolor 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.
drawerScrimColorcolor of the scrim that obscures content when the drawer is open
backgroundColorbackground of the scaffold body
contentColorcolor 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.
contentcontent of your screen. The lambda receives an [PaddingValues] that should be applied to the content root via Modifier.padding to properly offset top and bottom bars. If you're using VerticalScroller, apply this modifier to the child of the scroller, and not on the scroller itself.

Code Examples

SimpleScaffoldWithTopBar

@Composable
fun SimpleScaffoldWithTopBar() {
    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]))
                }
            }
        }
    )
}

ScaffoldWithBottomBarAndCutout

@Composable
fun ScaffoldWithBottomBarAndCutout() {
    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]))
                }
            }
        }
    )
}

ScaffoldWithSimpleSnackbar

@Composable
fun ScaffoldWithSimpleSnackbar() {
    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()
            )
        }
    )
}
by @alexstyl