Phase 2 · Building Interactive UIsModule 13~28 min read

Scaffold, App Bars & Structure

Give apps their standard structure with Scaffold, top bars, FABs, and snackbars.

What you'll learn

Nearly every screen shares the same skeleton: a bar on top, content in the middle, maybe a floating button and a place for messages. Material 3 gives you Scaffold to assemble that skeleton correctly — including the fiddly parts like keeping content from hiding behind the bars.

By the end you'll be able to:

  • Structure a screen with Scaffold and its slots
  • Add a TopAppBar with a title, navigation icon, and actions
  • Add a FloatingActionButton for the primary action
  • Show Snackbar messages with an optional action

The Scaffold

Scaffold is a layout with named slots for the standard pieces — topBar, bottomBar, floatingActionButton, snackbarHost — and a content lambda for the body. Crucially, it hands your content an innerPadding value: the space the bars occupy. You must apply it, or your first list item will hide under the app bar:

HomeScreen.kt
@Composable
fun HomeScreen() {
    Scaffold(
        topBar = { TopAppBar(title = { Text("Tasks") }) },
        floatingActionButton = {
            FloatingActionButton(onClick = { /* add */ }) {
                Icon(Icons.Default.Add, contentDescription = "Add task")
            }
        },
    ) { innerPadding ->
        // Content receives padding that avoids the bars — always apply it!
        LazyColumn(modifier = Modifier.padding(innerPadding)) {
            items(tasks) { task -> TaskRow(task) }
        }
    }
}
9:41
Tasks
Buy groceries
Call the dentist
Finish module 13
+
Scaffold — how this renders on a device

Watch out

The single most common Scaffold bug is ignoring innerPadding. Always pass it to your content's modifier (Modifier.padding(innerPadding)) — otherwise the top of your content slides under the TopAppBar.

App bars

TopAppBar holds the screen title and up to three kinds of controls: a navigationIcon on the left (a back arrow or menu), and actions on the right (search, overflow, etc.). There are variants — CenterAlignedTopAppBar, LargeTopAppBar — for different looks:

AppBar.kt
TopAppBar(
    title = { Text("Inbox") },
    navigationIcon = {
        IconButton(onClick = { /* open drawer / go back */ }) {
            Icon(Icons.Default.Menu, contentDescription = "Menu")
        }
    },
    actions = {
        IconButton(onClick = { /* search */ }) {
            Icon(Icons.Default.Search, contentDescription = "Search")
        }
    },
)

Floating action button

The FAB represents a screen's single most important action — compose a message, add an item, start a chat. Put it in the Scaffold's floatingActionButton slot so it floats above the content in the corner. Give it a clear icon and, always, a contentDescription for accessibility. Use at most one FAB per screen.

Snackbars

A Snackbar is a brief message at the bottom of the screen, optionally with a single action like "Undo." Showing one is a suspend function, so you trigger it from a coroutine scope tied to the composition. Wire a SnackbarHostState into the Scaffold's snackbarHost slot, then call showSnackbar:

UndoScreen.kt
@Composable
fun UndoScreen() {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },
    ) { padding ->
        Button(
            onClick = {
                scope.launch {                       // showing is a suspend call
                    val result = snackbarHostState.showSnackbar(
                        message = "Item deleted",
                        actionLabel = "Undo",
                    )
                    if (result == SnackbarResult.ActionPerformed) { /* undo */ }
                }
            },
            modifier = Modifier.padding(padding),
        ) { Text("Delete") }
    }
}

Tip

showSnackbar returns a SnackbarResult — check for ActionPerformed to know the user tapped "Undo." This is the standard pattern for reversible actions: act immediately, show a snackbar, and undo if they ask. We'll cover coroutine scopes properly in Phase 4; for now, rememberCoroutineScope() gives you a safe one.

Recap & quick check

Key takeaways

  • Scaffold provides slots for topBar, bottomBar, floatingActionButton, and snackbarHost, plus a content body.
  • Always apply the innerPadding Scaffold gives your content, or it hides behind the bars.
  • TopAppBar holds the title, a navigationIcon (back/menu), and actions (search, overflow).
  • Use at most one FloatingActionButton per screen for its primary action — with a contentDescription.
  • Snackbars show brief messages with an optional action; showSnackbar is a suspend call returning a result.

Quick check

1. What does Scaffold's content lambda receive that you must apply?

2. Where does the screen title go?

3. How many FABs should a screen have?

4. Why is showSnackbar called inside a coroutine scope?

5. How do you know the user tapped a snackbar's action?

Your screens are properly structured. To finish Phase 2, let's make them yours with a custom theme, dark mode, and dynamic color.