Compose: animations
Core principle
Pick the smallest API that expresses the motion and its lifecycle.
Procedure
- Identify the job: show or hide a subtree, animate one value, coordinate values from one state, resize content, swap content, or handle user-driven motion.
- Choose the matching API from the table. Prefer target-state APIs; use
Animatable only when gestures, interruption, or imperative control require it.
- Check lifecycle: an alpha animation keeps content composed;
AnimatedVisibility removes it after exit. Do not use a fade when unmounting is required.
- For
AnimatedContent, render from the content lambda target and choose a contentKey only when visual identity differs from payload equality. Read AnimatedContent identity for state-holder details.
- Keep animated
State in layout or draw block modifiers when it changes at frame rate; route deeper diagnosis to Compose performance.
- Use Navigation Compose transitions for destination swaps it owns, and dedicated libraries for art-based motion.
- Finish when the API, lifecycle, and content identity match the UI, no simpler API fits, and the relevant behavior is verified.
API choice
| Need |
Prefer |
| Show/hide a subtree with enter/exit semantics |
AnimatedVisibility |
| One value follows state |
animate*AsState |
| Several values follow one boolean, enum, or sealed state |
rememberTransition plus child animations |
| Child size changes |
Modifier.animateContentSize() |
| Different composable trees fill one region |
AnimatedContent, or Crossfade for the simple case |
| Drag, fling, interruption, or imperative control |
Animatable |
Use an AnimationSpec when the default motion is wrong and a distinct label when multiple animations need tooling visibility.
val width by animateDpAsState(
targetValue = if (expanded) 200.dp else 56.dp,
animationSpec = spring(dampingRatio = 0.7f),
label = "fabWidth",
)
For values that must remain synchronized, define them on one transition rather than several independent animate*AsState calls:
val transition = rememberTransition(targetState = phase, label = "phase")
val alpha by transition.animateFloat(label = "alpha") { target ->
if (target == Phase.Visible) 1f else 0f
}
val offset by transition.animateDp(label = "offset") { target ->
if (target == Phase.Visible) 0.dp else 24.dp
}
For animated fills, prefer drawBehind { drawRect(color.value) } over a value-form background when the color updates every frame. For an API ambiguity, start with the official Choose an animation API guide; use rememberInfiniteTransition for repeating cycles and SeekableTransitionState for seekable or test-controlled progress.
When not to use this skill
1---2name: compose-animations3description: Use when writing or reviewing Jetpack Compose motion: visibility enter/exit, animating one property toward a target, color or size transitions, multiple properties from one state, switching composable content, or choosing between AnimatedVisibility, animate*AsState, rememberTransition, AnimatedContent, and Crossfade.4---5
6# Compose: animations
7
8## Core principle
9
10Pick the smallest API that expresses the motion and its lifecycle.
11
12## Procedure
13
141. Identify the job: show or hide a subtree, animate one value, coordinate values from one state, resize content, swap content, or handle user-driven motion.
152. Choose the matching API from the table. Prefer target-state APIs; use `Animatable` only when gestures, interruption, or imperative control require it.
163. Check lifecycle: an alpha animation keeps content composed; `AnimatedVisibility` removes it after exit. Do not use a fade when unmounting is required.
174. For `AnimatedContent`, render from the content lambda target and choose a `contentKey` only when visual identity differs from payload equality. Read [AnimatedContent identity](references/animated-content.md) for state-holder details.
185. Keep animated `State` in layout or draw block modifiers when it changes at frame rate; route deeper diagnosis to [Compose performance](../compose-performance/SKILL.md).
196. Use Navigation Compose transitions for destination swaps it owns, and dedicated libraries for art-based motion.
207. Finish when the API, lifecycle, and content identity match the UI, no simpler API fits, and the relevant behavior is verified.
21
22## API choice
23
24| Need | Prefer |
25|---|---|
26| Show/hide a subtree with enter/exit semantics | [`AnimatedVisibility`](https://developer.android.com/develop/ui/compose/animation/composables-modifiers#animatedvisibility) |
27| One value follows state | `animate*AsState` |
28| Several values follow one boolean, enum, or sealed state | `rememberTransition` plus child animations |
29| Child size changes | `Modifier.animateContentSize()` |
30| Different composable trees fill one region | `AnimatedContent`, or `Crossfade` for the simple case |
31| Drag, fling, interruption, or imperative control | [`Animatable`](https://developer.android.com/reference/kotlin/androidx/compose/animation/core/Animatable) |
32
33Use an `AnimationSpec` when the default motion is wrong and a distinct `label` when multiple animations need tooling visibility.
34
35```kotlin
36val width by animateDpAsState(
37 targetValue = if (expanded) 200.dp else 56.dp,
38 animationSpec = spring(dampingRatio = 0.7f),
39 label = "fabWidth",
40)
41```
42
43For values that must remain synchronized, define them on one transition rather than several independent `animate*AsState` calls:
44
45```kotlin
46val transition = rememberTransition(targetState = phase, label = "phase")
47val alpha by transition.animateFloat(label = "alpha") { target ->
48 if (target == Phase.Visible) 1f else 0f
49}
50val offset by transition.animateDp(label = "offset") { target ->
51 if (target == Phase.Visible) 0.dp else 24.dp
52}
53```
54
55For animated fills, prefer `drawBehind { drawRect(color.value) }` over a value-form background when the color updates every frame. For an API ambiguity, start with the official [Choose an animation API](https://developer.android.com/develop/ui/compose/animation/choose-api) guide; use [`rememberInfiniteTransition`](https://developer.android.com/reference/kotlin/androidx/compose/animation/core/rememberInfiniteTransition) for repeating cycles and [`SeekableTransitionState`](https://developer.android.com/reference/kotlin/androidx/compose/animation/core/SeekableTransitionState) for seekable or test-controlled progress.
56
57## When not to use this skill
58
59- For side-effect timing or click-launched work, use [Compose state and effects](../compose-state-and-effects/SKILL.md).
60- For deep state-read or recomposition diagnosis, use [Compose performance](../compose-performance/SKILL.md).