---
title: Reduced motion
description: How Kapow reads the platform's reduced-motion setting on each target, what every effect does instead, and how to override it.
---

Some people turn motion off because it makes them ill or because they find it distracting. Kapow follows that setting on every platform and swaps each effect for a quiet version: a short fade or highlight, with no particles and no movement.

You do not have to call anything. Change and conditional effects check the setting each time they start; transitions check it when they compose.

## Where the setting comes from

| Target | Source |
| --- | --- |
| Android | `ValueAnimator.areAnimatorsEnabled()` on API 26 and later. Below API 26, Compose's `MotionDurationScale`, which is 0 when animations are off. That scale is read on all versions. |
| iOS | Reduce Motion in Accessibility settings. Compose also reports it as a motion duration scale of 0. |
| wasmJs | The `prefers-reduced-motion: reduce` media query. |
| JVM desktop | No system source. Motion is full unless you override it. |

On Android, both checks matter. Turning "Remove animations" on sets the animator duration scale to 0, and Compose passes that scale to coroutines as `MotionDurationScale`. Kapow treats a scale of 0 as reduced motion even on versions where the platform call is not available.

## What each effect does instead

| Effect | With reduced motion |
| --- | --- |
| `spray`, `rise` | No particles. The element briefly brightens. |
| `smoke` | Nothing. |
| `spin` | No rotation. The element briefly brightens. |
| `pulse` | No rings. The element's area flashes in the pulse color and fades. |
| `shine` | No sweep. A brief highlight that fades out. |
| `glow` | A faint, short halo that does not pulse. |
| `ripple` | No spreading circle. The element's shape tints in the ripple color and fades. |
| `flash` | The same flash, at 35 % strength instead of 85 %. |
| `burst` | No burst. The element briefly brightens. |
| `speedLines` | No lines. The element briefly brightens. |
| `sparkle` | No stars. The element briefly brightens. |
| `fireworks` | No rocket and no sparks. The element briefly brightens. |
| Conditional `glow` | The steady halo appears without a fade. |
| `borderBeam` | The beam stands still: the whole outline is lit evenly. |
| `marchingAnts` | The dashes stand still. |
| `rainbowBorder` | The gradient stands still. |
| `sparkles` | Nothing. |
| `orbit` | Nothing. |
| `bubbles` | Nothing. |
| `flame` | Nothing. |
| `grayscale` | The element turns gray without a fade. |
| `spotlight` | The dim appears without a fade. |
| `confettiPop` | No confetti. The element briefly brightens. |
| `trace` | Nothing travels. The whole outline appears at once and fades. |
| `strike` | The line appears whole instead of drawing across, then holds and fades. |
| `stamp` | No drop, no squash and no dust. The element briefly brightens. |
| `glitch` | No split and no shifted slices. The element briefly brightens. |
| `hearts` | One heart fades in and out in place. No small hearts. |
| `shimmer` | No band travels. The element is lightened evenly while the condition holds. |
| `breathe` | The element does not move. |
| `ping` | No growing rings. One faint ring stays still around the element while the condition holds. |
| `confettiRain` | No pieces fall. |
| `kapowSkeleton` | The placeholder is still, with no shimmer, and the reveal is a plain cross-fade. |
| `KapowTypewriter` | The full text shows at once, without a cursor. |
| `KapowCounter` | The new value appears at once. |
| `kapowTilt` | The element stays flat and draws no sheen. |
| Progress `fill` | Drawn at the progress you pass, like every progress effect. |
| Progress `trace` | Drawn at the progress you pass, like every progress effect. |
| Mask transitions: `iris`, `clock`, `wipe`, `glare`, `blinds`, `dissolve`, `curtain`, `shapeIris`, `checker`, `tear`, `blob` | A crossfade over the same duration or shorter, with no mask. |
| `poof`, `anvil`, `pop`, `shatter`, `disintegrate` | A fade over the same duration or shorter. No particles, no drop, no scale. |

Feedback calls are not affected. A haptic you asked for still fires under reduced motion.

The [effects pages](/effects) list the reduced behavior of every effect, including `repeat`, `pushDown` and the progress effects.

## Choosing the behavior per effect

Each effect has its own quiet version, listed above. To choose another one for a single effect, call `reducedMotion(mode)` on it, on change, conditional and progress effects and on `KapowEnter` and `KapowExit`. The `ReducedMotionMode` values are:

- `Default` keeps the effect's own fallback. It is what you get without the wrapper.
- `Nothing` draws nothing and sends no work to the frame loop. Feedback events still fire.
- `EndState` shows the effect's final look at once, without motion. Effects that end at rest draw nothing.
- `Fade` fades the final look in and out over a short time instead of moving.

For transitions, `Default` and `Fade` fade the content, and `Nothing` and `EndState` show or remove it at once, without a delay. For progress effects only `Nothing` has an effect. The mode applies only while reduced motion is on; with full motion it changes nothing.

```kotlin
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.ReducedMotionMode
import com.jantursky.kapow.changeEffect

@Composable
fun Score(score: Int) {
    BasicText(
        text = "$score",
        modifier = Modifier.changeEffect(
            score,
            ChangeEffect.confettiPop().reducedMotion(ReducedMotionMode.Nothing),
        ),
    )
}
```

## Overriding the setting

`LocalKapowMotion` overrides the platform for a part of the tree. It has three values:

- `KapowMotion.System`, the default, follows the platform.
- `KapowMotion.Full` always plays full effects.
- `KapowMotion.Reduced` always plays the reduced version.

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.jantursky.kapow.KapowMotion
import com.jantursky.kapow.LocalKapowMotion

@Composable
fun MotionSettings(reduceMotion: Boolean, content: @Composable () -> Unit) {
    CompositionLocalProvider(
        LocalKapowMotion provides if (reduceMotion) KapowMotion.Reduced else KapowMotion.System,
        content = content,
    )
}
```

On JVM desktop there is nothing to follow, so this is the way to offer an in-app switch. Use `Reduced` in tests to check the quiet path.

If you override to `Full`, you are playing motion for a user who asked for none. Do it for a preview or a demo, not for the app.

## JVM desktop

A desktop window has no system setting to follow, so offer one in your app and feed it to `LocalKapowMotion`. The sample keeps the choice in a checkbox row and provides it to everything below:

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import com.jantursky.kapow.KapowMotion
import com.jantursky.kapow.LocalKapowMotion

@Composable
fun DesktopApp(content: @Composable () -> Unit) {
    // Read this from your own settings store, so the choice survives a restart.
    var reduceMotion by remember { mutableStateOf(false) }
    Column {
        Row(
            Modifier.toggleable(
                value = reduceMotion,
                role = Role.Switch,
                onValueChange = { reduceMotion = it },
            ),
        ) {
            BasicText(if (reduceMotion) "Reduce motion: on" else "Reduce motion: off")
        }
        CompositionLocalProvider(
            LocalKapowMotion provides if (reduceMotion) KapowMotion.Reduced else KapowMotion.System,
            content = content,
        )
    }
}
```

`System` is the right default: on the desktop it means full motion today, and it follows the platform wherever a system source exists. A user who turned motion off in an operating system setting cannot be detected on the JVM, which is why the in-app switch matters.

## Reading it yourself

`isReducedMotion()` is a composable that returns the value effects here would use. It resolves `LocalKapowMotion` first, then the platform. Use it to match your own animations to Kapow's.

```kotlin
import androidx.compose.runtime.Composable
import com.jantursky.kapow.isReducedMotion

@Composable
fun rememberCardDuration(): Int = if (isReducedMotion()) 0 else 300
```

## Not verified on a device yet

The Android, iOS and browser detection is covered by tests and by running the demo. Reading the Reduce Motion setting on a real iPhone has not been checked, and neither has wasm in Safari.
