Conditional effects
Effects that stay on while a Boolean is true, how repeat plays a change effect on a timer, and how fadeIn, fadeOut, startDelay, speed and reducedMotion tune them.
A conditional effect runs while a condition is true. Modifier.conditionalEffect(condition, effect) starts the effect when the condition becomes true, including on first composition, and winds it down when the condition becomes false. The element then returns to its normal look.
The effects are repeat, glow, pushDown, smoke, borderBeam, marchingAnts, rainbowBorder, sparkles, orbit, bubbles, flame, grayscale, spotlight, shimmer, breathe, ping and confettiRain.
Glow
A steady halo in the element’s shape. It fades in while the condition holds and fades out when it ends.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.jantursky.kapow.ConditionalEffect
import com.jantursky.kapow.conditionalEffect
@Composable
fun SelectedCard(selected: Boolean) {
val shape = RoundedCornerShape(16.dp)
Box(
Modifier
.size(160.dp)
.background(Color.DarkGray, shape)
.conditionalEffect(selected, ConditionalEffect.glow(shape = shape)),
)
}
The halo follows the shape you pass, not the alpha of the content. Where the platform has no blur (Android below API 31) it is drawn from stacked outlines instead.
Push down
Scales the element to 95 % while the condition is true and springs back when it ends. Pass scale for another amount.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.jantursky.kapow.ConditionalEffect
import com.jantursky.kapow.conditionalEffect
@Composable
fun HoldButton(pressed: Boolean) {
Box(
Modifier
.size(96.dp)
.background(Color.Gray)
.conditionalEffect(pressed, ConditionalEffect.pushDown()),
)
}
Smoke
Soft particles rise from the top of the element while the condition is true. When it turns false, no new particles start and the ones in flight finish their life. Smoke is a particle effect, so a particle layer keeps a clipping parent from cutting it off.
Border beam
A short, bright beam travels around the element’s outline on a faint track, for something live or in progress, such as a recording. With reduced motion the whole outline is lit evenly instead. See border beam.
Marching ants
A dashed outline whose dashes keep moving along the element’s shape, the look of a selection in an editor. The pattern is stretched a little so the dashes meet without a seam. See marching ants.
Rainbow border
An outline stroked with a sweep gradient of several colors that turns around the element, for something premium such as the featured plan. See rainbow border.
Sparkles
Four-point stars twinkle in and out over the element and just past its edges, a few at a time. They are particles, so a particle layer keeps a clipping parent from cutting them off. See sparkles.
Orbit
A few glowing particles circle the element on a tipped ellipse, passing behind it smaller and fainter and in front of it at full size. The element draws them itself, outside its bounds. See orbit.
Bubbles
Small, see-through bubbles rise from the top edge, wobble and pop. Like smoke, they are particles: when the condition turns false the ones in flight finish rising. See bubbles.
Flame
Procedural flames above the element: soft puffs rise, shrink and turn from yellow through red to transparent, drawn with radial gradients rather than sprites or shaders. See flame.
Grayscale
The element fades to gray through a color filter on a layer of its own, the look of something disabled or unavailable. Pass amount to keep some color. See grayscale.
Spotlight
Dims everything around the element and leaves its shape clear, with a soft edge, as an onboarding tip does. The element draws the scrim itself, far past its bounds, so content drawn after it stays on top and clipping parents cut it off. See spotlight.
Shimmer
A band of light sweeps the element’s drawn pixels again and again, with a pause between sweeps, the look of a loading placeholder. See shimmer.
Breathe
The element swells a little and settles back on a slow loop, and can float up and down, to say that something is alive. When the condition ends it rests on a spring with no jump. See breathe.
Ping
Rings grow out of the element’s outline and fade, like a radar ping or a live indicator, in the element’s shape. When the condition ends the rings in flight finish. See ping.
Confetti rain
Confetti keeps falling from the element’s top edge, tumbling and fading, with at most 200 pieces in the air. It is made of particles, so a particle layer lets it fall past clipping parents. See confetti rain.
Repeat
ConditionalEffect.repeat(effect, every) plays a change effect as soon as the condition becomes true and then once every every while it stays true. It is how you make a badge pulse or a button shine every few seconds.
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.ConditionalEffect
import com.jantursky.kapow.conditionalEffect
import kotlin.time.Duration.Companion.seconds
@Composable
fun UpdateAvailable(available: Boolean) {
BasicText(
text = "Update available",
modifier = Modifier.conditionalEffect(
condition = available,
effect = ConditionalEffect.repeat(ChangeEffect.shine(), every = 4.seconds),
),
)
}
Things to know:
- Intervals shorter than 1/15 s are raised to 1/15 s. The floor is
ConditionalEffect.MinRepeatInterval. - When the condition turns false no further repetitions are scheduled. A repetition that is already playing finishes.
- The wrapped change effect keeps its own cooldown. A
shinewith a 1 s duration has a 0.5 s cooldown, so repeating it every 0.2 s still plays it about every 0.5 s.
Fade, delay, speed and reduced motion
Four wrappers work on any conditional effect, repeat included. Each returns a new ConditionalEffect.
| Wrapper | What it does |
|---|---|
.fadeIn(duration) |
How long the effect takes to fade in when the condition turns true, instead of its own fade. |
.fadeOut(duration) |
How long the effect takes to fade out when the condition turns false, instead of its own fade. |
.startDelay(delay) |
Waits delay after the condition turns true before the effect starts. |
.speed(multiplier) |
Runs the effect at multiplier times the pace: 2 is twice as fast. Repeated calls multiply. |
.reducedMotion(mode) |
Chooses what the effect does under reduced motion. See Reduced motion. |
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.jantursky.kapow.ConditionalEffect
import com.jantursky.kapow.ReducedMotionMode
import com.jantursky.kapow.conditionalEffect
import kotlin.time.Duration.Companion.milliseconds
@Composable
fun LiveBadge(live: Boolean) {
Box(
Modifier
.size(48.dp)
.background(Color.DarkGray)
.conditionalEffect(
live,
ConditionalEffect.ping()
.startDelay(500.milliseconds)
.fadeOut(300.milliseconds)
.speed(0.8f)
.reducedMotion(ReducedMotionMode.Nothing),
),
)
}
See Tuning effects for the options all effects share.