Haze is a library for backdrop-style visual effects in Jetpack Compose and Compose Multiplatform. Up until now, blur has been the headline feature and the architecture has been built around that assumption.
In 2.0.0-alpha01, that changes. Blur is now just one effect implementation, built on top of a more general rendering pipeline. That makes the core smaller, the APIs more flexible, and opens the door to custom effects beyond blur.
The Problem#
Haze started as a background blur library for Compose Multiplatform, and it did that job well. But blur is just one visual effect, and the monolithic design of v1 made it hard to add new effects, compose multiple effects together, or let anyone build their own. Adding something like a gradient effect shouldn’t require understanding the blur internals.
In reality, blurring turned out to be a small piwce of the overall problem of ‘background blurring’: the majority of the code in Haze has always been concerned with drawing background content.
Introducing VisualEffect#
Haze 2.0 introduces a new VisualEffect interface. The core library no longer knows about blur specifically. Instead, it provides the effect-agnostic infrastructure: coordinate calculation, layer management, scroll-aware positioning, noise texturing, and progressive rendering. Effects like blur implement the VisualEffect interface and plug into that infrastructure.
This means:
- Blur is now one implementation. Extracted to a separate
haze-blurmodule. - Custom effects are possible. Want gradient overlays, color shifting, or custom shaders? Implement
VisualEffectand plug it in. - Smaller core module. If you don’t need blur, you don’t pay for it.
That architectural split is the main goal of 2.0. Blur is still there, but it no longer defines the entire library.
Migration at a Glance#
If you’re using blur you now need the blur module:
implementation("dev.chrisbanes.haze:haze-blur:2.0.0-alpha01")Blur properties now live inside a blurEffect {} block:
// v1
Modifier.hazeEffect(state = hazeState) {
blurRadius = 20.dp
tint = HazeTint(Color.White.copy(alpha = 0.3f))
}
// v2
Modifier.hazeEffect(state = hazeState) {
blurEffect {
blurRadius = 20.dp
colorEffects = listOf(HazeColorEffect.tint(Color.White.copy(alpha = 0.3f)))
}
}Key renames:
| v1 | v2 |
|---|---|
HazeStyle | HazeBlurStyle |
HazeTint | HazeColorEffect |
HazeProgressive | HazeProgressive (unchanged, but moved to haze-blur package) |
rememberHazeState(blurEnabled) | blurEffect { blurEnabled = ... } |
Full migration guide: chrisbanes.github.io/haze/latest/migrating-2.0/
What’s New Beyond the Refactor#
Alongside the architecture work, 2.0.0-alpha01 also ships with a few other notable changes:
- Configurable
HazePositionStrategy: fixes blur misalignment in split-window scenarios on Android, where window-relative positioning could previously be wrong - Migration helpers:
blurRadiusconvenience extension onHazeEffectScopefor the common single-effect blur case - Materials API promoted to stable:
@ExperimentalHazeMaterialsApiannotation removed - Performance improvements: per-frame allocation eliminated, effect updates gated, snapshot edge fixes
Getting Started#
The important thing to note here is that the core module now provides the infrastructure. If you want the familiar blur effect, you need haze-blur.
val hazeState = rememberHazeState()
Box(
modifier = Modifier
.fillMaxSize()
.hazeSource(state = hazeState)
) {
LazyColumn { /* your content */ }
}
Box(
modifier = Modifier
.fillMaxSize()
.hazeEffect(state = hazeState) {
blurEffect {
blurRadius = 20.dp
}
}
)What’s Next#
This is an alpha. The VisualEffect interface and the blurEffect {} API block are the shape I’m aiming for, but I want community feedback before locking them in as stable.
If you try it out, I’d especially love feedback on:
- Whether
VisualEffectfeels flexible enough for custom effects - Whether
blurEffect {}feels ergonomic in day-to-day use - Which built-in effects you’d want to see beyond blur?
If you hit issues or have feedback, please file them on GitHub.
