Scaffold & TopAppBar

Every non-trivial screen needs somewhere consistent to put a title bar, maybe a bottom navigation bar, maybe a floating "add" button, and the actual scrollable content in between — all without those pieces overlapping or fighting each other for space. Scaffold is Compose's answer to that layout problem: a single composable that reserves named slots for the standard pieces of a Material screen and takes care of the padding math between them. From here on, every RecipeBox screen we build — starting with the recipe list — will be wrapped in a Scaffold.

Scaffold's Slots

Scaffold doesn't take a single content lambda like a plain layout — it exposes several optional, named slots as parameters, and you fill in only the ones your screen actually needs:

ParameterPurpose
topBarUsually a TopAppBar — title, navigation icon, actions
bottomBarA bottom navigation bar or bottom app bar
floatingActionButtonA single FAB, positioned per floatingActionButtonPosition
snackbarHostWhere snackbars are anchored (covered in the next lesson but one)
contentThe main screen body — Scaffold hands it PaddingValues to avoid overlapping the other slots

TopAppBar: Title, Navigation Icon & Actions

TopAppBar (from androidx.compose.material3) takes a title composable, an optional navigationIcon slot (typically a back arrow), and an actions row rendered on the trailing edge:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RecipeListTopBar(
    onSearchClick: () -> Unit,
    onBackClick: (() -> Unit)? = null
) {
    TopAppBar(
        title = { Text("My Recipes") },
        navigationIcon = {
            if (onBackClick != null) {
                IconButton(onClick = onBackClick) {
                    Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
                }
            }
        },
        actions = {
            IconButton(onClick = onSearchClick) {
                Icon(Icons.Filled.Search, contentDescription = "Search recipes")
            }
        }
    )
}

TopAppBar is marked @ExperimentalMaterial3Api in current Material 3 releases, which is why the composable that hosts it needs an @OptIn annotation — this is normal and expected, not a sign something is broken.

FloatingActionButton: Adding a Recipe

A FloatingActionButton is a circular, elevated button reserved for the single most important action on a screen. On the recipe list, that's adding a new recipe:

@Composable
fun AddRecipeFab(onAddClick: () -> Unit) {
    FloatingActionButton(onClick = onAddClick) {
        Icon(Icons.Filled.Add, contentDescription = "Add recipe")
    }
}

Worked Example: The RecipeBox Recipe List Screen

Putting the pieces together, here is a full recipe list screen built on Scaffold. Notice how content's innerPadding parameter is applied to the LazyColumn so its first and last items never sit underneath the top bar or FAB:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RecipeListScreen(
    recipes: List<Recipe>,
    onRecipeClick: (Recipe) -> Unit,
    onAddClick: () -> Unit,
    onSearchClick: () -> Unit
) {
    Scaffold(
        topBar = { RecipeListTopBar(onSearchClick = onSearchClick) },
        floatingActionButton = { AddRecipeFab(onAddClick = onAddClick) }
    ) { innerPadding ->
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding),
            contentPadding = PaddingValues(12.dp)
        ) {
            items(recipes, key = { it.id }) { recipe ->
                Text(
                    text = recipe.title,
                    modifier = Modifier
                        .fillMaxWidth()
                        .clickable { onRecipeClick(recipe) }
                        .padding(16.dp)
                )
            }
        }
    }
}

Scaffold's content lambda always receives the reserved padding as its single argument — forgetting to apply it (.padding(innerPadding)) is the single most common Scaffold mistake, and it results in list content sliding underneath the top bar as soon as the user scrolls.

Note: This is correct, real Compose/Kotlin code — but this website's own project doesn't run a live Android Studio build, so you won't see it actually render on this page.
Next up: Dialogs & AlertDialog adds a confirmation dialog to this same recipe list — for example, confirming before a recipe is deleted.