| name | haze-patterns |
| description | Expert guidance on common Haze usage patterns and recipes. Use when implementing Scaffold blur, sticky headers, overlapping effects, dialog blur, or deep hierarchy state sharing with Haze. |
Haze Patterns — Common Recipes
Instructions
1. Scaffold with Blurred App Bars
Multiple hazeEffect modifiers share one HazeState:
val hazeState = rememberHazeState()
Scaffold(
topBar = {
TopAppBar(
colors = TopAppBarDefaults.largeTopAppBarColors(Color.Transparent),
modifier = Modifier.hazeEffect(hazeState) {
blurEffect { style = HazeMaterials.thin() }
}
)
},
bottomBar = {
NavigationBar(
containerColor = Color.Transparent,
modifier = Modifier.hazeEffect(hazeState) {
blurEffect { style = HazeMaterials.thin() }
}
)
}
) { padding ->
LazyColumn(
modifier = Modifier.padding(padding).hazeSource(hazeState)
) { }
}
[!KEY]
App bars must have transparent background colors to see the blur effect.
2. Dialog Blur
Blur dialog backgrounds over underlying content:
val hazeState = rememberHazeState()
var showDialog by remember { mutableStateOf(false) }
Box {
LazyVerticalGrid(
modifier = Modifier.hazeSource(state = hazeState)
) { }
if (showDialog) {
Dialog(onDismissRequest = { showDialog = false }) {
Surface(
modifier = Modifier.fillMaxWidth().fillMaxHeight(0.5f),
shape = MaterialTheme.shapes.extraLarge,
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.2f),
) {
Box(Modifier.hazeEffect(state = hazeState) {
blurEffect { style = HazeMaterials.regular() }
}) {
}
}
}
}
}
[!IMPORTANT]
With dialogs, colorEffects tints display as a scrim. Use a translucent color on the dialog Surface instead.
3. Sticky Headers
Apply hazeSource on all non-header items, hazeEffect on the sticky header:
val hazeState = rememberHazeState()
LazyColumn {
stickyHeader {
Header(
modifier = Modifier.hazeEffect(hazeState) {
blurEffect { style = HazeMaterials.thin() }
}
)
}
items(list) { item ->
Foo(modifier = Modifier.hazeSource(hazeState))
}
}
Avoid recursive drawing: don't put hazeSource on the LazyColumn and hazeEffect on items within it.
4. Overlapping Blurred Cards
A composable can be both source and effect target simultaneously using zIndex:
Box {
val hazeState = rememberHazeState()
AsyncImage(
modifier = Modifier.fillMaxSize().hazeSource(hazeState, zIndex = 0f),
model = "...", contentDescription = null
)
CreditCard(
modifier = Modifier
.hazeSource(hazeState, zIndex = 1f)
.hazeEffect(hazeState) { blurEffect { style = HazeMaterials.thin() } }
)
CreditCard(
modifier = Modifier
.hazeSource(hazeState, zIndex = 2f)
.hazeEffect(hazeState) { blurEffect { style = HazeMaterials.thin() } }
)
CreditCard(
modifier = Modifier
.hazeSource(hazeState, zIndex = 3f)
.hazeEffect(hazeState) { blurEffect { style = HazeMaterials.thin() } }
)
}
[!NOTE]
By default, a hazeEffect draws all layers with zIndex less than its sibling hazeSource.
5. Filtering Source Areas
Exclude specific layers from an effect via canDrawArea:
CreditCard(
modifier = Modifier
.hazeSource(hazeState, zIndex = 2f, key = "self")
.hazeEffect(hazeState) {
canDrawArea = { area -> area.key != "self" }
blurEffect { }
}
)
6. Deep Hierarchy — Composition Local
Avoid prop drilling through nested composables:
val LocalHazeState = compositionLocalOf { HazeState() }
@Composable
fun Screen() {
val hazeState = rememberHazeState()
CompositionLocalProvider(LocalHazeState provides hazeState) {
Box {
ContentLayer()
OverlayLayer()
}
}
}
@Composable
fun OverlayLayer() {
TopAppBar(
modifier = Modifier.hazeEffect(state = LocalHazeState.current) {
blurEffect { style = HazeMaterials.thin() }
}
)
}
7. Integrated Source and Effect on Same Node
A layout can use both hazeSource and hazeEffect on itself:
Card(
modifier = Modifier
.hazeSource(hazeState, zIndex = 1f)
.hazeEffect(hazeState) {
blurEffect { style = HazeMaterials.thin() }
}
) {
}
8. Position Strategy for Split-Window
Fix blur misalignment in split-window modes (e.g., Huawei Parallel Space):
val hazeState = rememberHazeState(positionStrategy = HazePositionStrategy.Local)
9. Checklist