Skip to main content
TopBar provides a top app bar component with support for scroll behaviors including pinned, enter-always, and exit-until-collapsed modes.

Parameters

modifier
Modifier
default:"Modifier"
Modifier to be applied to the top bar
scrollBehavior
TopBarScrollBehavior?
default:"null"
Scroll behavior that defines how the top bar responds to scrolling. Can be pinned, enter-always, or exit-until-collapsed
colors
TopBarColors
default:"TopBarDefaults.topBarColors()"
Colors for the top bar container in default and scrolled states
windowInsets
WindowInsets?
default:"TopBarDefaults.windowInsets"
Window insets for the top bar. Defaults to system bars on top and horizontal sides
content
@Composable () -> Unit
required
Content of the top bar, typically containing title and actions

TopBarColors

containerColor
Color
default:"AppTheme.colors.background"
Background color of the top bar
scrolledContainerColor
Color
default:"AppTheme.colors.background"
Background color when content is scrolled behind the top bar

Scroll Behaviors

Pinned Scroll Behavior

The top bar remains pinned at the top and doesn’t move:
val scrollBehavior = TopBarDefaults.pinnedScrollBehavior()

TopBar(
    scrollBehavior = scrollBehavior
) {
    Text("Pinned Top Bar")
}

Enter Always Scroll Behavior

The top bar scrolls with content and enters the screen when scrolling up:
state
TopBarState
default:"rememberTopBarState()"
State object for managing scroll offsets
canScroll
() -> Boolean
default:"{ true }"
Callback to determine if scrolling is allowed
snapAnimationSpec
AnimationSpec<Float>?
default:"spring(stiffness = Spring.StiffnessMediumLow)"
Animation spec for snapping behavior
flingAnimationSpec
DecayAnimationSpec<Float>?
default:"rememberSplineBasedDecay()"
Animation spec for fling gestures
val scrollBehavior = TopBarDefaults.enterAlwaysScrollBehavior()

TopBar(
    scrollBehavior = scrollBehavior
) {
    Text("Enter Always Top Bar")
}

Exit Until Collapsed Scroll Behavior

The top bar scrolls off screen when scrolling down and appears when scrolling up:
val scrollBehavior = TopBarDefaults.exitUntilCollapsedScrollBehavior()

TopBar(
    scrollBehavior = scrollBehavior
) {
    Text("Collapsing Top Bar")
}

Usage Examples

Basic Top Bar

TopBar {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        IconButton(onClick = { /* Menu */ }) {
            Icon(Icons.Default.Menu, contentDescription = "Menu")
        }
        Text(
            text = "My App",
            style = AppTheme.typography.h2
        )
        IconButton(onClick = { /* Settings */ }) {
            Icon(Icons.Default.Settings, contentDescription = "Settings")
        }
    }
}

Top Bar with Pinned Behavior

val scrollBehavior = TopBarDefaults.pinnedScrollBehavior()

Scaffold(
    topBar = {
        TopBar(scrollBehavior = scrollBehavior) {
            Text(
                text = "Pinned Header",
                modifier = Modifier.padding(16.dp),
                style = AppTheme.typography.h2
            )
        }
    }
) { paddingValues ->
    LazyColumn(
        modifier = Modifier
            .padding(paddingValues)
            .fillMaxSize()
    ) {
        items(100) {
            Text("Item $it", modifier = Modifier.padding(16.dp))
        }
    }
}

Top Bar with Enter Always Behavior

val scrollBehavior = TopBarDefaults.enterAlwaysScrollBehavior()
val lazyListState = rememberLazyListState()

Scaffold(
    topBar = {
        TopBar(scrollBehavior = scrollBehavior) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.Center
            ) {
                Text(
                    text = "Scrolling Header",
                    style = AppTheme.typography.h2
                )
            }
        }
    }
) { paddingValues ->
    LazyColumn(
        state = lazyListState,
        modifier = Modifier
            .padding(paddingValues)
            .nestedScroll(scrollBehavior.nestedScrollConnection)
    ) {
        items(100) {
            ListItem(
                headlineContent = { Text("Item $it") }
            )
            HorizontalDivider()
        }
    }
}

Top Bar with Exit Until Collapsed

val scrollBehavior = TopBarDefaults.exitUntilCollapsedScrollBehavior()

Scaffold(
    topBar = {
        TopBar(scrollBehavior = scrollBehavior) {
            Column(
                modifier = Modifier.fillMaxWidth(),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    text = "Collapsing Header",
                    style = AppTheme.typography.h1
                )
                Text(
                    text = "Subtitle",
                    style = AppTheme.typography.body2
                )
            }
        }
    }
) { paddingValues ->
    LazyColumn(
        modifier = Modifier
            .padding(paddingValues)
            .nestedScroll(scrollBehavior.nestedScrollConnection)
    ) {
        items(50) {
            Text(
                text = "Content $it",
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

Top Bar with Custom Colors

TopBar(
    colors = TopBarDefaults.topBarColors(
        containerColor = AppTheme.colors.primary,
        scrolledContainerColor = AppTheme.colors.primaryVariant
    )
) {
    Text(
        text = "Custom Colors",
        color = AppTheme.colors.onPrimary,
        modifier = Modifier.padding(16.dp)
    )
}

Top Bar with Actions

TopBar {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) {
            IconButton(onClick = { /* Back */ }) {
                Icon(Icons.Default.ArrowBack, "Back")
            }
            Text(
                text = "Detail Page",
                style = AppTheme.typography.h2
            )
        }
        
        Row {
            IconButton(onClick = { /* Share */ }) {
                Icon(Icons.Default.Share, "Share")
            }
            IconButton(onClick = { /* More */ }) {
                Icon(Icons.Default.MoreVert, "More")
            }
        }
    }
}

TopBarState

State object for managing scroll behavior:
val topBarState = rememberTopBarState(
    initialHeightOffsetLimit = -Float.MAX_VALUE,
    initialHeightOffset = 0f,
    initialContentOffset = 0f
)

val scrollBehavior = TopBarDefaults.enterAlwaysScrollBehavior(
    state = topBarState
)

TopBarState Properties

  • heightOffsetLimit: Float: Maximum offset the bar can collapse
  • heightOffset: Float: Current offset of the bar
  • contentOffset: Float: Offset of the content
  • collapsedFraction: Float: 0f (fully expanded) to 1f (fully collapsed)
  • overlappedFraction: Float: Fraction of content overlapping the bar

Advanced Examples

var searchExpanded by remember { mutableStateOf(false) }
var searchQuery by remember { mutableStateOf("") }

TopBar {
    if (searchExpanded) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            IconButton(onClick = { searchExpanded = false }) {
                Icon(Icons.Default.ArrowBack, "Close search")
            }
            TextField(
                value = searchQuery,
                onValueChange = { searchQuery = it },
                placeholder = { Text("Search...") },
                modifier = Modifier.weight(1f)
            )
        }
    } else {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text("My App")
            IconButton(onClick = { searchExpanded = true }) {
                Icon(Icons.Default.Search, "Search")
            }
        }
    }
}

Top Bar with Color Transition

val scrollBehavior = TopBarDefaults.enterAlwaysScrollBehavior()
val fraction = scrollBehavior.state.overlappedFraction

TopBar(
    scrollBehavior = scrollBehavior,
    colors = TopBarDefaults.topBarColors(
        containerColor = Color.Transparent,
        scrolledContainerColor = AppTheme.colors.surface
    )
) {
    Text(
        text = "Color Transition",
        modifier = Modifier.padding(16.dp),
        color = lerp(
            AppTheme.colors.onBackground,
            AppTheme.colors.onSurface,
            fraction
        )
    )
}

Constants

  • Height: 56dp (TopBarDefaults.TopBarHeight)
  • Animation Stiffness: Spring.StiffnessMediumLow

Features

  • Scroll Behaviors: Three built-in scroll behaviors
  • Smooth Animations: Color and position transitions
  • Window Insets: Automatic handling of system bars
  • State Management: Saveable state across configuration changes
  • Drag Support: Manual dragging when not pinned
  • Nested Scroll: Integration with Compose nested scroll system
  • Accessibility: Proper traversal group semantics

Best Practices

  1. Connect scroll behavior: Use nestedScroll(scrollBehavior.nestedScrollConnection) on scrollable content
  2. Respect window insets: Use default window insets for proper edge-to-edge display
  3. Color contrast: Ensure text remains readable during color transitions
  4. Action placement: Put primary actions on the right, navigation on the left

Source Reference

TopBar component implementation: components-lab/src/commonMain/kotlin/com/nomanr/lumo/ui/components/topbar/TopBar.kt

Build docs developers (and LLMs) love