Features
Installation
Add the dependency to the source set that holds your screens: commonMain in a Compose
Multiplatform project, the app module in an Android-only project.
dependencies {
implementation("am.highapps.parallaxtoolbar:compose-parallax-toolbar-kmp:2.0.0")
}
iOS, desktop and web apps use it from their Kotlin shared module; nothing is imported on the
Swift or JavaScript side. A Swift-only app adds one small Kotlin module for the screen, since
Compose has no Swift API. The platform guide shows the setup and the one
iOS Info.plist key Compose needs.
Quick start
import am.highapps.parallaxtoolbar.ComposeParallaxToolbarLayout
import am.highapps.parallaxtoolbar.ParallaxContent
am.highapps.parallaxtoolbar.ParallaxToolbarDefaults
{
ComposeParallaxToolbarLayout(
titleContent = { collapsed ->
Text(
album.title,
style = (collapsed) MaterialTheme.typography.titleMedium MaterialTheme.typography.headlineMedium
)
},
subtitleContent = { Text(album.artist) },
headerContent = {
Image(album.cover, contentDescription = , Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
},
navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, ) } },
actions = { IconButton(onClick = onShare) { Icon(Icons.Default.Share, ) } },
content = ParallaxContent.Lazy(
content = { collapsed -> items(album.tracks) { TrackRow(it) } },
config = ParallaxToolbarDefaults.lazyColumnConfig(contentPadding = PaddingValues(dp))
)
)
}
The Material calls are the app's choice; the library has no Material dependency. Every slot
receives collapsed and runs in a ParallaxToolbarScope, which also exposes the continuous
collapseFraction. Use ParallaxContent.Regular for a scrolling column, or
ParallaxContent.Custom to bring your own scrollable:
content = ParallaxContent.Custom {
LazyVerticalGrid(GridCells.Fixed(2), Modifier.fillMaxSize()) { items(photos) { PhotoCell(it) } }
}
Configuration
Everything is set through small immutable configs built by ParallaxToolbarDefaults:
ComposeParallaxToolbarLayout(
headerConfig = ParallaxToolbarDefaults.headerConfigWithPercentage(
heightPercentage = 0.4f,
maxHeight = 320.dp,
scrollMode = ScrollMode.EnterAlways,
snapOnRelease = true,
stretchEnabled = true
),
onStretchTrigger = { refresh() },
toolbarConfig = ParallaxToolbarDefaults.toolbarConfig(targetColor = MaterialTheme.colorScheme.surface, elevation = 3.dp),
titleConfig = ParallaxToolbarDefaults.titleConfig(collapsedScale = 0.8f, collapsedAlignment = Alignment.CenterHorizontally),
bottomContent = { TabRow() },
contentPadding = padding
)
Two parameters sit on the layout itself: windowInsets, which defaults to the status bar plus
the display cutout and takes WindowInsets(0) when the layout does not touch the window edge,
and collapseEnabled, which locks the header for loading or editing states. The
API reference lists every parameter and default.
Scroll modes
 ExitUntilCollapsed (default) | |
Scrolling up always collapses the header first. What happens on the way back down is the mode:
expand only once the body is at its top, expand immediately anywhere, or slide the toolbar away
too and bring it back first.
headerConfig = ParallaxToolbarDefaults.headerConfig(scrollMode = ScrollMode.EnterAlwaysCollapsed)
Snap and title alignment
 snapOnRelease |  collapsedAlignment and collapsedScale |
headerConfig = ParallaxToolbarDefaults.headerConfig(snapOnRelease = true),
titleConfig = ParallaxToolbarDefaults.titleConfig(collapsedAlignment = Alignment.CenterHorizontally, collapsedScale = 0.85f)
Bottom slot, pull-to-refresh and overlay
 bottomContent and stretchEnabled |  |
bottomContent pins a row under the toolbar. It rides the header's bottom edge while expanded
and stays put once collapsed; the body starts beneath it. stretchEnabled lets a pull past the
top stretch the header; release springs it back and, past stretchTriggerDistance, calls
onStretchTrigger. overlayContent sits above the body and the toolbar, so an element there can
glide from the header into the toolbar with moveBetween.
bottomContent = { TabRow() },
headerConfig = ParallaxToolbarDefaults.headerConfig(stretchEnabled = true),
onStretchTrigger = { viewModel.refresh() },
overlayContent = {
Avatar(Modifier.size(72.dp).moveBetween(expanded = Alignment.BottomEnd, collapsed = Alignment.CenterEnd, collapsedScale = 0.5f))
}
State and effects
val state = rememberParallaxToolbarState()
val scope = rememberCoroutineScope()
ComposeParallaxToolbarLayout(, state = state)
Text("${(state.collapseFraction * 100).toInt()} %")
Button(onClick = { scope.launch { state.collapse() } }) { Text("Collapse") }
Inside any slot the scope offers modifiers for per-element effects:
headerConfig = ParallaxToolbarDefaults.headerConfig(parallaxMultiplier = 0f, fadeOnCollapse = false),
headerContent = {
Image(cover, null, Modifier.fillMaxSize().parallax(0.5f).fadeOnCollapse())
Text("Est. 1998", Modifier.align(Alignment.BottomEnd).scaleOnCollapse(0.6f).fadeOnCollapse())
ChipRow(Modifier.align(Alignment.BottomStart).pin(stopAtTop = true))
}
Documentation
Using an AI coding assistant? Point it at llms.txt, or drop
docs/agents/SKILL.md into your project's agent instructions. It holds the
current signatures, the rules that matter, and the 1.x habits to avoid.
Sample app
Try the playground in your browser,
no install needed; ?preset=pin, ?preset=rtl or ?preset=grid-avatar open a feature directly.
The sample module is an interactive playground shared by Android, iOS, desktop and web. It
opens on a photo header with the library defaults; a bar at the bottom moves the header, shows
the collapse progress and switches the scroll mode, and its gear opens a sheet with presets,
the fixed example screens and every setting. See sample/README.md for how
to run each host.
Compatibility
The library depends only on org.jetbrains.compose.ui:ui and org.jetbrains.compose.foundation:foundation.
Versioning
Semantic versioning: breaking changes ship only in a major version. The public API is explicit
and its dump under compose-parallax-toolbar-kmp/api/ is checked on every pull request. A
deprecated API keeps working for at least one minor release, carries a ReplaceWith, and is
removed in the next major. Report bugs through issues
and security concerns as described in SECURITY.md.
Contributing
See CONTRIBUTING.md. The project follows the code of conduct.
Acknowledgments
The original motion was inspired by Morad Azzouzi's article
Collapsing toolbar with parallax effect and curve motion in Jetpack Compose.
Author
Created by Hayk Arustamyan. If the library saves you time,
a star on GitHub or a coffee is appreciated.
License
MIT