---
title: Change effects
description: How a change effect decides to play, and what equality, isEnabled, cooldown, delay, times, speed and reducedMotion do.
---

A change effect plays once each time a value you watch changes. You attach it with `Modifier.changeEffect(value, effect)`.

```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.changeEffect

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

The effects are `shine`, `pulse`, `glow`, `spray`, `rise`, `spin`, `ripple`, `flash`, `burst`, `speedLines`, `sparkle`, `fireworks`, `confettiPop`, `hearts`, `trace`, `strike`, `stamp` and `glitch`. The [effects overview](/effects) has one line on each, and [Tuning effects](/guides/tuning-effects) covers the options they share.

## When it plays

Kapow keeps the last value it saw on the element. Each time the modifier is applied with a new value:

- **First composition is silent.** The value you start with is only recorded.
- **Equal values do nothing.** The comparison is `!=`. Replacing `3` with `3`, or a list with another list that is equal, plays nothing.
- **A different value plays the effect once.**

The value can be anything: an `Int`, a `Boolean`, an enum, a data class. If you want to replay for the same data, for example a "ping" button, watch a counter that you increase on each tap instead.

```kotlin
import androidx.compose.foundation.clickable
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.jantursky.kapow.ChangeEffect
import com.jantursky.kapow.changeEffect

@Composable
fun PingButton() {
    var pings by remember { mutableIntStateOf(0) }
    BasicText(
        text = "Ping",
        modifier = Modifier
            .clickable { pings++ }
            .changeEffect(pings, ChangeEffect.glow()),
    )
}
```

## isEnabled

`isEnabled` defaults to `true`. While it is `false`, changes are recorded but play nothing. Turning it back on does not replay old changes. Use it to stay quiet during a bulk update or while the screen is not visible.

```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.changeEffect

@Composable
fun Total(total: Int, isLoading: Boolean) {
    BasicText(
        text = "$total",
        modifier = Modifier.changeEffect(total, ChangeEffect.shine(), isEnabled = !isLoading),
    )
}
```

## Cooldown

Some effects ignore a change that arrives too soon after the last one they accepted. The cooldown is part of the effect, not a parameter:

| Effect | Cooldown |
| --- | --- |
| `shine` | half of its `duration` |
| `pulse` | 0.2 s for each pulse after the first (`count - 1`) |
| `trace` | half of its `duration` |
| `glow`, `spray`, `rise`, `spin`, `ripple`, `flash`, `burst`, `speedLines`, `sparkle`, `fireworks`, `confettiPop`, `hearts`, `strike`, `stamp`, `glitch` | none |

`.cooldown(...)` replaces the effect's own cooldown, as the next section shows. With the default 1 s `shine`, two changes 0.2 s apart play one sweep. The effects without a cooldown play on every change: `rise` floats two labels for two quick changes, `glow` gets stronger up to a limit, and `spin` adds speed.

## Delaying an effect

`.delay()` wraps any change effect so it starts a fixed time after the change is accepted. Delays add up if you nest them. The cooldown of the wrapped effect still applies.

```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.changeEffect
import kotlin.time.Duration.Companion.milliseconds

@Composable
fun Reward(coins: Int) {
    BasicText(
        text = "Coins: $coins",
        modifier = Modifier.changeEffect(coins, ChangeEffect.spray().delay(300.milliseconds)),
    )
}
```

## Cooldown, times, speed and reduced motion

Four more wrappers work on any change effect. Each returns a new `ChangeEffect`, and they can be chained in any order.

| Wrapper | What it does |
| --- | --- |
| `.cooldown(duration)` | Ignores a change that arrives sooner than `duration` after the last one that played. It replaces the effect's own cooldown, so `.cooldown(Duration.ZERO)` turns it off. |
| `.times(count, gap)` | Plays the effect `count` times for each change, `gap` apart. Changes that arrive while the series runs are ignored. |
| `.speed(multiplier)` | Plays the effect at `multiplier` times the pace: 2 takes half as long, 0.5 twice as long. Repeated calls multiply. |
| `.reducedMotion(mode)` | Chooses what the effect does under reduced motion. See [Reduced motion](/guides/reduced-motion). |

```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
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@Composable
fun Likes(likes: Int) {
    BasicText(
        text = "$likes",
        modifier = Modifier.changeEffect(
            likes,
            ChangeEffect.pulse()
                .cooldown(2.seconds)
                .times(count = 2, gap = 150.milliseconds)
                .speed(1.5f)
                .reducedMotion(ReducedMotionMode.Nothing),
        ),
    )
}
```

A count or a multiplier that is not positive, or a negative duration, throws when you build the effect.

## Feedback

Pass `feedback = KapowFeedbackEvent.ImpactLight` to ask for a haptic or a sound when the effect plays. It is also called under reduced motion. See [Feedback](/guides/feedback).

## No recomposition per frame

Only the value change recomposes. The animation runs inside the modifier node and invalidates drawing, not composition. [Performance](/guides/performance) has the details.
