Skip to content

Blur usageΒΆ

Blur works with either source-backed content or the modifier's own content. Both modes use the typed hazeBlur modifier and the same replayable Style.

Source-backed BlurΒΆ

val hazeState = rememberHazeState()
val style = HazeMaterials.thin()

Box {
  LazyColumn(
    modifier = Modifier
      .fillMaxSize()
      .hazeSource(hazeState),
  ) {
    // Content
  }

  TopAppBar(
    modifier = Modifier.hazeBlur(
      input = HazeInput.Sources(hazeState),
      style = style,
    ),
  )
}

HazeInput.Sources also owns source selection and retained-output behavior. The default KeepLastFrame policy avoids an empty flash during source transitions. Use ClearWhenUnavailable for privacy-sensitive content:

Modifier.hazeBlur(
  input = HazeInput.Sources(
    state = hazeState,
    retention = HazeSourceRetention.ClearWhenUnavailable,
  ),
)

Own-content BlurΒΆ

Use HazeInput.Content when the modifier's own content is the input:

Image(
  modifier = Modifier.hazeBlur(
    input = HazeInput.Content,
    style = HazeMaterials.thin(),
  ),
)

Enabling BlurΒΆ

Blur is enabled by default only where Haze considers the platform implementation reliable. To override that decision, write blurEnabled in a Style:

Modifier.hazeBlur(
  input = HazeInput.Sources(hazeState),
  style = HazeBlurStyle {
    blurEnabled(true)
  },
)

When Blur is disabled, Haze draws the configured fallback scrim instead.

Replayable StylesΒΆ

HazeBlurStyle is an opaque program of Blur-specific writes:

val style = HazeBlurStyle {
  blurEnabled(true)
  blurRadius(20.dp)
  noiseFactor(0.15f)
  backgroundColor(Color.Black)
  colorEffects(
    listOf(
      HazeColorEffect.tint(Color.White.copy(alpha = 0.12f)),
    ),
  )
  fallbackColorEffect(HazeColorEffect.tint(Color.Black.copy(alpha = 0.7f)))
  alpha(1f)
  mask(null)
  progressive(null)
  blurredEdgeTreatment(BlurredEdgeTreatment.Rectangle)
}

Style resolution always replays these tiers in order:

  1. HazeBlurDefaults.style
  2. LocalHazeBlurStyle
  3. The explicit hazeBlur Style

The last write to a property wins, both across tiers and within a Style chain:

val compact = HazeMaterials.thin().then {
  blurRadius(12.dp)
  noiseFactor(0f)
}

If a replacement Style omits blurRadius, the local or default value becomes visible again. Styles are immutable and safe to share; create a replacement Style when the appearance needs to change.

Caller-owned color-effect lists are snapshotted when the Style is created. An explicit empty list clears inherited color effects:

val noColorEffects = HazeBlurStyle {
  colorEffects(emptyList())
}

Progressive Blur and masksΒΆ

Progressive Blur varies intensity across the surface:

val progressiveStyle = HazeBlurStyle {
  progressive(
    HazeProgressive.verticalGradient(
      startIntensity = 1f,
      endIntensity = 0f,
    ),
  )
}

A mask fades the effect's opacity and is usually cheaper:

val maskedStyle = HazeBlurStyle {
  mask(
    Brush.verticalGradient(
      colors = listOf(Color.Black, Color.Transparent),
    ),
  )
}

Input scaleΒΆ

Sampling and layer expansionΒΆ

Sampling and layer expansion are structural modifier policies, not Style properties:

Modifier.hazeBlur(
  input = HazeInput.Sources(hazeState),
  style = style,
  sampling = HazeSampling.Adaptive,
  expandLayerBounds = true,
)
  • HazeSampling.Default and Adaptive let Blur balance quality and cost automatically.
  • HazeSampling.FullResolution disables input downscaling.
  • HazeSampling.Fixed(pixelFraction) retains a fixed fraction of the full-resolution input pixels when you need a predictable trade-off.

Start with the default. Override it only after comparing visual quality and performance on the devices you support.