GlassΒΆ
A refraction-driven Glass effect that combines refraction, depth blur, tint, Fresnel/ambient lift, and specular highlights with optional rounded shapes and dispersion.
DownloadΒΆ
Glass is published to Maven Central starting with Haze 2.0.0-alpha04. Use the same version for
the core and Glass artifacts:
dependencies {
implementation("dev.chrisbanes.haze:haze:<version>")
implementation("dev.chrisbanes.haze:haze-glass:<version>")
}
For unreleased changes, follow the snapshot build instructions and use the same snapshot version for both artifacts.
Material 3ΒΆ
Add the optional Material 3 integration when a Glass surface should use the current theme surface color:
dependencies {
implementation("dev.chrisbanes.haze:haze-glass-material3:<version>")
}
GlassStyle.Material3() uses MaterialTheme.colorScheme.surface as its default container color.
Pass containerColor to use a different color. Supply a replacement Style through recomposition
when the theme changes. It deliberately leaves the tint unset unless you pass one, so it never
derives a tint from LocalContentColor.
Apply Material 3 to a built-in or custom Style with material3():
val regular = GlassStyle.regular.material3()
val clear = GlassStyle.clear.material3(tint = Color.White.copy(alpha = 0.16f))
Modifier.hazeGlass(
input = HazeInput.Backdrop(hazeState),
style = GlassStyle.Material3(tint = Color.White.copy(alpha = 0.16f)) {
optics(refractionStrength = 0.8f)
},
)
The factory writes its container color and optional tint before its block, allowing the block to override either value. It also works as a subtree default:
CompositionLocalProvider(LocalGlassStyle provides GlassStyle.Material3()) {
// Glass modifiers in this subtree inherit the Material 3 surface background.
}
Built-in stylesΒΆ
GlassStyle.regular is the default built-in Glass style. Its blur and depth adapt to the
surface's shortest dimension and include a restrained edge fold, where the local sampling direction
reverses so incoming content can appear inverted near the glass boundary. This makes it a good fit
for reusable components.
GlassStyle.clear is the alternative built-in style for surfaces that should keep more of the
background visible. Its blur and depth increase smoothly with the surface's shortest side, while
its authored refraction and distinct edge and lighting response remain recognizable on renderers
that simplify advanced optical effects. These are Haze styles informed by the platform distinction;
they do not promise pixel parity with another system.
Modifier.hazeGlass(
input = HazeInput.Backdrop(hazeState),
style = GlassStyle.clear.material3(tint = Color.White.copy(alpha = 0.12f)),
)
ParametersΒΆ
- backgroundColor: Color composited behind captured content before refraction and blur (defaults to transparent). Use an opaque color when transparent captured content must fully obscure the original sharp source.
- tint: Glass tint (defaults to transparent).
- optics: Optical material configuration.
GlassStyle.regularsupplies the default size-aware optics. Calloptics(...)for inline fixed configuration, or keep aGlassOpticsvalue when it needs to be reused or selected programmatically. - refractionDetailIntensity: Secondary edge-refraction detail strength
0..1.0disables the detail pass. Caller-authoredGlassOpticsdefaults to0.76; Regular uses0, and Clear uses0.76. Non-zero values may retain additional rendering layers on full renderers. - specularIntensity: Highlight strength
0..1(default 0.48). - edgeShadow: Shape-following dark rim composited beneath the specular highlight (defaults to
black at 14% opacity). Its alpha controls strength; set
Color.Transparentto disable it. - ambientResponse: Fresnel/edge lift
0..1(default 0.46). - edgeSoftness: Soft fade at the edges (default 2.dp). Set to 0.dp for hard edges.
- shape (
RoundedCornerShape): Rounded-rect boundary for refraction and masking (default 16.dp corners). - surfaceProfile: Cross-section profile for the refraction bezel. Options:
Circle(default),Squircle,Lip,Concave. - lightPosition:
Alignmentof the light within the material's measured bounds (defaultAlignment.Center). Logical start and end follow the node's layout direction. - chromaticAberrationStrength: Dispersion strength
0..1(default 0). Higher values produce prismatic color splitting at edges. - chromaticAberrationMode: Quality mode for chromatic aberration.
Simple(default, fast) orFull(spectral, more expensive). - alpha: Overall opacity multiplier
0..1(default 1).
Glass validates configuration when a Style or GlassOptics value is created instead of
silently correcting it later. Validate or clamp values from user input and remote data before
building the Style. The generated API reference documents the accepted range for each property.
GlassStyleΒΆ
GlassStyle is immutable and safe to share. Build a base Style, use then for variations, and
provide a replacement Style through recomposition when the appearance changes. Values omitted by
the replacement fall back to LocalGlassStyle and then the individual GlassDefaults values.
A Style captures its inputs when it is constructed. Changing captured state does not update an existing Style; construct and provide a replacement instead.
Accessibility preferencesΒΆ
Haze leaves platform accessibility-service integration to the host application, then accepts its
semantic result through LocalGlassAccessibilitySettings. This keeps the common API portable and
does not attempt to reproduce another platform's system controls. Provide settings at the subtree
where Glass is used:
CompositionLocalProvider(
LocalGlassAccessibilitySettings provides GlassAccessibilitySettings(
reduceTransparency = reduceTransparency,
increaseContrast = increaseContrast,
showBorders = showBorders,
),
) {
// Glass here reduces background diffusion or strengthens its separating edge when requested.
}
The default settings preserve the authored Style exactly. These preferences are applied at render resolution, so they do not mutate or replace the Style supplied by your app. Reduced Transparency reduces the authored blur contribution. Increased Contrast and Show Borders enforce a visible edge, even when the authored Style has zero edge softness.
val baseStyle = GlassStyle {
backgroundColor(Color.White)
tint(Color.White.copy(alpha = 0.16f))
optics(refractionStrength = 0.8f)
shape(RoundedCornerShape(20.dp))
}
val emphasizedStyle = baseStyle.then { specularIntensity(0.7f) }
CompositionLocalProvider(LocalGlassStyle provides baseStyle) {
// Use baseStyle as the default for Glass in this subtree.
}
Light alignmentΒΆ
Use Compose Alignment values so lighting adapts to each surface. Logical alignments such as
Alignment.CenterStart and Alignment.CenterEnd also follow LTR or RTL layout direction.
val sharedLighting = GlassStyle {
lightPosition(Alignment.CenterStart)
}
Use BiasAlignment when the light needs a continuous proportional position rather than a named
alignment.
val movingLighting = GlassStyle {
lightPosition(BiasAlignment(horizontalBias = 0.4f, verticalBias = -0.6f))
}
Style defaultsΒΆ
GlassStyle.regular is the default built-in material response. Individual omitted values fall back
to GlassDefaults. Use LocalGlassStyle to set a default for a subtree, and pass an explicit Style
when one element needs to differ.
Box(
Modifier
.size(180.dp)
.hazeGlass(input = HazeInput.Backdrop(hazeState)),
)
Choosing opticsΒΆ
Use GlassStyle.regular for the built-in Haze material, which adapts blur and depth to the
material's shortest dimension. Use GlassOptics when authoring a custom Style with direct optical
control:
val regular = GlassStyle.regular
GlassStyle {
optics(
blurRadius = 20.dp,
refractionStrength = 0.8f,
refractionHeightFraction = 0.3f,
refractionDisplacement = 18.dp,
refractionFoldStrength = 0.65f,
depth = 0.5f,
)
}
The scalar Style overload creates fixed values. For a responsive custom configuration, provide independent shortest-dimension points for blur and depth; values clamp at the endpoints and use a smoothstep interpolation between points. Each responsive value requires at least two points with positive, finite, strictly increasing dimensions:
val responsiveOptics = GlassOptics(
blurRadius = OpticalSizeValue.Responsive(
OpticalSizePoint(176.dp, 8.dp),
OpticalSizePoint(300.dp, 12.dp),
),
depth = OpticalSizeValue.Fixed(0.4f),
)
val style = GlassStyle { optics(responsiveOptics) }
Keep a complete value when it is reused, stored, copied, or selected programmatically:
val reusableOptics = GlassOptics(
blurRadius = OpticalSizeValue.Fixed(20.dp),
)
val style = GlassStyle { optics(reusableOptics) }
refractionFoldStrength controls the inverted edge-refraction fold from 0f to 1f. The default
for GlassOptics is 0f, which preserves the original monotonic refraction map. The fold is
available with every SurfaceProfile and remains within the configured refraction displacement;
it does not expand the capture area.
refractionDetailIntensity controls a secondary edge-detail pass on full renderers. Set it to 0f
to disable the pass and its additional retained layers. Caller-authored GlassOptics defaults to
0.76f; GlassStyle.regular uses 0f, while GlassStyle.clear uses 0.76f.
GlassOptics controls the appearance; HazePerformanceMode.Fixed controls the normalized
rendering trade-off.
The shape supplied to Glass defines its material boundary. Add an outer Modifier.clip() with
the same shape only when child content also needs clipping.
Retained outputΒΆ
Glass can retain and redraw its last captured output when all source areas disappear. This
keeps source transitions smooth, but can briefly preserve stale pixels from removed source content.
Keep the default for smooth transitions, and keep source ownership explicit with
HazeInput.Sources:
Box(
Modifier
.size(180.dp)
.hazeGlass(
input = HazeInput.Sources(hazeState),
style = GlassStyle,
)
)
The default is HazeSourceRetention.KeepLastFrame. For privacy-sensitive source content, opt out
of retaining pixels when the source disappears:
Modifier.hazeGlass(
input = HazeInput.Sources(
state = hazeState,
retention = HazeSourceRetention.ClearWhenUnavailable,
),
style = GlassStyle,
)
Android window-backdrop GlassΒΆ
HazeInput.Backdrop is the normal Glass input when the surface should consume the combined pixels
already drawn earlier in the current window. Its native path is experimental and eligible only
after setting the process-wide flag before the effect node attaches:
HazeFeatureFlags.isPlatformBackdropEnabled = true
Modifier.hazeGlass(
input = HazeInput.Backdrop(hazeState),
style = GlassStyle.regular,
)
The native path reuses Glass's fused blur, depth, refraction, detail, chromatic, tint, and tone graph at compositor resolution. Rim and interaction lighting remain independent foreground draws, and authored alpha, shape, and material transforms keep their normal ordering. It does not retain a source or fused-output layer while healthy.
Backdrop input samples the current window's combined earlier pixels. It cannot select a Haze
source, include content drawn later, or cross a dialog, popup, or window boundary. The fallback
source input on HazeInput.Backdrop configures only its source fallback; it does not filter native
window pixels. The current flag default is false, and
true means eligible rather than guaranteed: the built-in effect, full Android 37.2 gate, window,
canvas, native graph setup, and draw must all succeed. Unsupported platforms or a native failure
select the source fallback for the rest of that attachment; the transition may take one frame.
Changing the flag affects later attachments only, and healthy native rendering does not retain or
record source layers.
Set HazeLogger.enabled = true to inspect selection and fallback messages. Native work is marked
by the HazeBackdrop.draw trace section. This is diagnostic information, not a performance claim;
physical Android 37.2 acceptance remains a separate release gate. A later release may enable the
flag by default, retain a temporary false escape hatch, and then remove the experimental flag.
Use Sources directly when exact source selection, cross-window alignment, or retained-output behavior must govern the actual input rather than only the fallback. ADR-0010 defines the input and rollout, while ADR-0003 remains authoritative for source-backed Android Glass.
You can select a literal optical configuration when the built-in material does not fit the design:
GlassStyle {
tint(Color.White.copy(alpha = 0.20f))
optics(
progressive = HazeProgressive.verticalGradient(
startIntensity = 1f,
endIntensity = 0.25f,
),
)
}
FallbacksΒΆ
Use one GlassStyle on every platform. Haze chooses the available implementation automatically, so
applications do not need capability checks or a second fallback Style.
Fallback rendering keeps the material recognizable but may simplify advanced optics:
| Authored behavior | Preferred rendering | Fallback |
|---|---|---|
| Tint, alpha, and rounded shape | Preserved | Preserved |
| Ambient edge response and edge softness | Preserved | Approximated as a soft rim |
Specular intensity and resolved light Alignment |
Preserved | Approximated as an aligned radial highlight |
| Interaction lighting and transforms | Preserved | Preserved |
| Fixed or responsive refraction, blur, and progressive optics | Preserved | Omitted |
| Chromatic aberration, surface profile, and advanced color adjustments | Preserved | Omitted |
| Interaction refraction and white-point deltas | Preserved | Omitted |
Do not make essential meaning depend on refraction or chromatic aberration alone, because those details may be omitted by a fallback.
PerformanceΒΆ
Start with HazePerformanceMode.Default (the adaptive performance mode) and tune only after
measuring a representative screen. The performance guide explains
which Glass-specific workloads and interactions to test.
InteractionΒΆ
Glass interaction is default-disabled and entirely opt-in. It adds a visual response only: it does not add click handling, focusability, semantics, or keyboard/D-pad activation.
Modifier.hazeGlass(
input = HazeInput.Backdrop(hazeState),
style = GlassStyle {
pressed {
lightingIntensity(1f)
refractionMultiplier(1.08f)
whitePointDelta(0.04f)
scale(0.98f)
}
},
)
Declare only the states and response channels the material needs. To make focus and keyboard/D-pad
activation useful, retain one MutableInteractionSource and share it with both the glass effect
and your behavior modifiers:
val interactionStyle = GlassStyle {
hovered { lightingIntensity(0.35f) }
focused { lightingIntensity(0.35f) }
pressed {
lightingIntensity(1f)
refractionMultiplier(1.08f)
whitePointDelta(0.04f)
scale(0.98f)
}
interactionLightRadiusFraction(0.7f)
interactionPositionAnimationSpec(
spring(dampingRatio = 1f, stiffness = Spring.StiffnessMedium),
)
}
val interactionSource = remember { MutableInteractionSource() }
Modifier
.clickable(interactionSource = interactionSource, indication = null) { onClick() }
.focusable(interactionSource = interactionSource)
.hazeGlass(
input = HazeInput.Backdrop(hazeState),
style = interactionStyle,
interactionSource = interactionSource,
interactionTransformTarget = GlassTransformTarget.MaterialAndContent,
interactionTransformPivot = GlassTransformPivot.Pointer,
interactionReducedMotionPolicy = GlassReducedMotionPolicy.System,
)
Direct mouse, stylus, and touch positions track immediately.
interactionPositionAnimationSpec controls movement to focus and InteractionSource-derived
positions.
Keep the visual response in GlassStyle, and pass each element's interaction source and behavior
options to hazeGlass. The same Style can be reused without sharing interaction state.
When states overlap, pressed takes priority over hovered, which takes priority over focused. Use
animate(toSpec, fromSpec) when arrival and departure need different motion.
GlassStyle {
pressed {
animate(
toSpec = spring(dampingRatio = 0.82f, stiffness = Spring.StiffnessMedium),
fromSpec = spring(
dampingRatio = 0.72f,
stiffness = Spring.StiffnessMediumLow,
),
) {
scale(0.98f)
}
}
}
interactionTransformTarget selects whether a response transforms only the material or also its
content. interactionTransformPivot selects Pointer or Center. Omit a state block from a
replacement Style to remove it.
GlassReducedMotionPolicy.System follows the available system duration scale, Reduced snaps
lighting and optics while suppressing transforms, and Full forces motion.
UsageΒΆ
Box(
Modifier
.size(180.dp)
.hazeGlass(
input = HazeInput.Backdrop(hazeState),
style = GlassStyle {
tint(Color.White.copy(alpha = 0.16f))
optics(
refractionStrength = 0.8f,
refractionHeightFraction = 0.32f,
depth = 0.5f,
)
specularIntensity(0.7f)
ambientResponse(0.7f)
edgeSoftness(14.dp)
shape(RoundedCornerShape(20.dp))
surfaceProfile(SurfaceProfile.Squircle)
chromaticAberrationStrength(0.2f)
},
)
)
TipsΒΆ
GlassStyle.regularis the right starting point for material-like glass that should respond naturally to its shortest dimension. Use directoptics(...)for inline fixed authoring andGlassOpticswhen the complete value needs to be reused or selected programmatically.- Keep
chromaticAberrationStrengthmodest; start at 0.1-0.25 to avoid rainbow artifacts. - Combine
edgeSoftnesswith rounded shapes for smooth clipping; setedgeSoftness = 0.dpto rely purely on the shape. - Use
SurfaceProfile.Concavefor an inward-curving bezel orSurfaceProfile.Lipfor a raised rim effect.