Open-source project
chrisbanes/haze avatar
chrisbanes/haze

chrisbanes/haze: Background Blur and Glass Effects for Compose Multiplatform

Background blurring for Compose Multiplatform / Jetpack Compose

2,579 stars84 forksKotlinApache-2.0

At a glance

What is it?
Haze 2.0 is a Kotlin library that captures Compose content and re-renders it as blur or refraction-driven glass on Android, iOS, macOS, Desktop and Web. It is modular, explicit about configuration, and its Glass module is still experimental.
Who is it for?
Adopt Haze 2 if you are building Compose Multiplatform UI on Android, iOS, macOS, Desktop or Web and want source-backed blur without writing your own graphics-layer capture. Do not adopt it if you need a stable Glass API (haze-glass requires @ExperimentalHazeApi) or if your preview is drawn into a SurfaceView, because a SurfaceView cannot be captured.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Kotlin, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Haze Solves for Compose Multiplatform

Compose draws into a graphics layer, and a blur behind a surface needs the pixels of whatever sits underneath it. Doing that by hand means capturing another composable's output, feeding it into an effect, and keeping the capture in step with scrolling and recomposition. Haze packages that loop. The README describes it as "hardware-accelerated visual effects for Compose Multiplatform" for Android, iOS, macOS, Desktop and Web, and the platform table marks all five as supported.

The intended audience is app developers building Compose UI who want an iOS-style or Material-style translucent surface without dropping to platform-specific rendering. Haze 2 splits the work across artifacts rather than one monolith: haze for source capture and the custom-effect API, haze-blur for blur, haze-glass for the refraction effect, plus material presets. That split matters if you only need blur, because you are not pulling in the glass machinery. It also matters for upgrade cost, since the README says to keep every Haze artifact on the same version.

How the Source Capture and Rendering Pipeline Works

The mechanism is a shared state object. You call rememberHazeState(), mark the content you want captured with Modifier.hazeSource(hazeState), and then apply an effect modifier that reads from that state. In the README's blur example, an Image is the source and a sibling Box covering the same area applies hazeBlur with input = HazeInput.Sources(hazeState).

The input type is the fork in the road. HazeInput.Sources renders content captured through hazeSource, which is what you want for a blurred header over a scrolling list. HazeInput.Content blurs the modifier's own content instead, which is a different job: the effect applies to what that composable draws, not to what is behind it. Getting this wrong is the most likely first mistake, because both produce a blur and the difference only shows when content moves underneath.

Styles are data, not lambdas evaluated per frame. The README states that HazeBlurStyle is immutable and reusable, and that you derive a variation with then or supply a replacement Style when the appearance changes. That is a deliberate constraint: a style you build inside a composable body on every recomposition works against the design. The same pattern carries into Glass, where GlassStyle.regular.then { ... } sets tint and shape.

For camera and platform views, the constraint is the graphics layer. Blur and Glass can process a live camera preview when its pixels are drawn in the same Compose graphics layer as hazeSource. On Android, CameraX requires PreviewView.ImplementationMode.COMPATIBLE, and a SurfaceView cannot be captured at all. That is a hard boundary, not a tuning knob.

Installing haze-blur and Applying Your First Blur

Haze is published to Maven Central under the group dev.chrisbanes.haze, so it arrives through your normal Gradle dependency block. Add the core artifact and the effect artifact you need, and keep them on one version.

kotlin
dependencies {
    implementation("dev.chrisbanes.haze:haze:<version>")
    implementation("dev.chrisbanes.haze:haze-blur:<version>")
}

That is the minimum for blur. The README lists haze-blur-materials as optional ready-made Blur Styles and haze-blur-material3 as an optional Compose Material 3 factory; add them only if you want the presets. For Glass, the README gives a separate block with haze and the experimental haze-glass artifact.

The first real use is a source and an effect over it. The README's example puts a painter-backed Image and a matching Box inside a Box, with the state created once outside the layout.

kotlin
val hazeState = rememberHazeState()

Box {
    Image(
        painter = painter,
        contentDescription = null,
        modifier = Modifier.hazeSource(hazeState),
    )
    Box(
        modifier = Modifier
            .matchParentSize()
            .hazeBlur(
                input = HazeInput.Sources(hazeState),
                style = HazeBlurStyle { blurRadius(20.dp) },
            ),
    )
}

What you should see is the image underneath, blurred by a 20.dp radius, with the second Box sized to the first. Change blurRadius and the effect follows, because the style is immutable and you are supplying a new one. If nothing blurs, check that the source modifier and the effect modifier are in the same layout subtree and that you passed HazeInput.Sources rather than HazeInput.Content.

If you want a preset instead of a hand-built style, the README names HazeMaterials.ultraThin(), thin(), regular(), thick() and ultraThick(), and points to the Blur materials page for the full list.

Glass Is Experimental, and the API Says So

The haze-glass module is the most interesting part of Haze 2 and the part to treat with the most caution. The README calls it experimental and states that Glass APIs require the @ExperimentalHazeApi annotation. That annotation is the honest signal: the optics, the style surface and the Material 3 integration can move between releases.

What it does is broader than blur. The README lists refraction, depth blur, tint, lighting, highlights, rounded shapes, and optional pointer, focus and press responses. The starting point is GlassStyle.regular, with GlassStyle.clear offered when the background should stay more visible.

kotlin
Modifier.hazeGlass(
    input = HazeInput.Sources(hazeState),
    style = GlassStyle.regular.then {
        tint(Color.White.copy(alpha = 0.16f))
        shape(RoundedCornerShape(20.dp))
    },
)

The Material 3 integration is optional and changes behaviour rather than just colours: the README notes that Regular and Clear follow the system appearance without Material 3, while adding haze-glass-material3 supplies the app theme's surface colour and can add a tint. That is a real design decision you have to make, not a cosmetic one. The README also points to a Glass guide for custom optics, interaction, retention and platform fallback behaviour, which implies fallback handling is something you configure rather than something the library decides for you.

Performance Modes and the Measurement You Still Have to Do

Haze ships a performance mode enum, and the default is not a neutral choice. Built-in Blur and Glass use HazePerformanceMode.Default, which selects the fixed Balanced profile. Quality, Performance and Fixed(...) are the alternatives, and Adaptive remains only as a deprecated compatibility alias for Default.

The README is direct about what to do: begin with the default and measure a release-like build on representative devices before selecting another profile. That instruction is worth taking literally. A debug build on a desktop JVM tells you almost nothing about a mid-range Android phone, and the fixed Balanced profile is a compromise that a flagship may not need. There is no published benchmark in the README, so the only number that matters is the one you produce.

Custom effects sit outside this system. The README states that they keep the separate HazeSampling policy, so if you are writing your own effect rather than using blur or glass, the performance mode enum does not govern your sampling. That is a documentation gap worth noting: the README names HazeSampling but does not explain it here, deferring to the performance guide.

Where Haze Is the Wrong Tool

The clearest failure mode is documented rather than discovered: a SurfaceView cannot be captured. If your camera, video or map preview renders into a SurfaceView, Haze will not see those pixels. On Android with CameraX you must set PreviewView.ImplementationMode.COMPATIBLE, which routes the preview through the Compose graphics layer and typically costs you the more efficient path. The README applies the same constraint to video and other platform views. If your architecture depends on SurfaceView-backed playback, Haze is the wrong tool and no configuration fixes it.

The second limit is version discipline. The README says to keep every Haze artifact on the same version. Mixing haze-blur from one release with haze from another is not a supported arrangement, and the modular design makes that mistake easier to commit than a single-artifact library would.

The third is the beta status of Haze 2 itself. The README states that Haze 2.0 is currently in beta and points anyone upgrading from Haze 1.x or an earlier Haze 2 prerelease to a migration guide. If you are on Haze 1.x, this is a migration, not a version bump.

Haze Compared with a Plain Modifier.blur

Compose already offers Modifier.blur, and for some designs that is the right answer. The difference is what each one operates on. A plain blur modifier applies to the composable it is attached to. Haze's HazeInput.Sources applies to content captured elsewhere in the tree, which is what you need when the thing being blurred is behind the surface rather than inside it.

That distinction decides the choice. A blurred image inside a card: use Modifier.blur and skip the dependency. A translucent header that blurs the list scrolling underneath it, or a glass panel that refracts the content behind it: Haze is built for exactly that, and reproducing it by hand means managing your own graphics-layer capture. Haze also brings the material presets and the Material 3 style factories, which a plain modifier does not have. What it does not bring is a stable Glass API, so if the refraction effect is the reason you are here, you are adopting an experimental surface.

Editorial conclusion

Adopt Haze 2 if you are building Compose Multiplatform UI on Android, iOS, macOS, Desktop or Web and want source-backed blur without writing your own graphics-layer capture. Do not adopt it if you need a stable Glass API (haze-glass requires @ExperimentalHazeApi) or if your preview is drawn into a SurfaceView, because a SurfaceView cannot be captured. Before committing, verify two things: that every Haze artifact in your build sits on the same version, and that HazePerformanceMode.Default on a release-like build on your own devices gives acceptable frame times before you move to Quality or Performance.

Frequently asked questions

Which platforms does chrisbanes/haze support?

The README's platform table marks Android, Desktop (JVM), iOS, macOS and Wasm/JS as supported. Haze describes itself as hardware-accelerated visual effects for Compose Multiplatform across those targets.

Which Haze artifacts do I need to add for blur?

The README's download block adds dev.chrisbanes.haze:haze and dev.chrisbanes.haze:haze-blur for core infrastructure and blur. haze-blur-materials is listed as optional for ready-made Blur Styles, and the README says to keep every Haze artifact on the same version.

Can Haze blur a live camera preview?

Blur and Glass can process a live camera preview when its pixels are drawn in the same Compose graphics layer as hazeSource. On Android, CameraX requires PreviewView.ImplementationMode.COMPATIBLE, and the README states that a SurfaceView cannot be captured.

Official sources

  1. chrisbanes/haze on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chrisbanes-haze.svg)](https://hysenlabs.com/projects/chrisbanes-haze)