compose-motion
Jetpack Compose animation foundations - animate*AsState, AnimatedVisibility, Crossfade, updateTransition, SharedTransitionLayout, gestures.
Install / Use
npx skills add AThevon/genjutsu --skill compose-motionInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Customer SupportSupported Platforms
Our assessment of compose-motion
compose-motion scores 91/100 on our quality scale, 128th of 320 Customer Support skills we index (top 40%).
Its SKILL.md is 14 KB long, well organised into 20 sections with 17 code examples: a thorough specification that gives an agent plenty to work with.
It has 370 GitHub stars, a meaningful sign that others use it.
Maintenance, license and trust
- The repository was last updated 26 days ago, so compose-motion is actively maintained.
- No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
- Its trust signals score 88/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-10-05. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
compose-motion compared with similar skills
All 4 of these similar skills score higher than compose-motion; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| compose-motion (this skill)by AThevon | 91 | 370 | 26d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 90.8k | 19d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 74.4k | today | CLAUDE.md |
| Scraplingby D4Vinci | 100 | 85.7k | today | MCP Server |
| crawl4aiby unclecode | 100 | 84.8k | 9d ago | MCP Server |
Frequently asked questions
- How do I install compose-motion?
- Run
npx skills add AThevon/genjutsu --skill compose-motion. The install tabs above show the steps for each supported agent. - Which AI agents does compose-motion work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is compose-motion safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It declares no license and scores 88/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is compose-motion still maintained?
- The repository was last updated 26 days ago, so compose-motion is actively maintained.
Skill content
View source on GitHubname: compose-motion description: "Jetpack Compose animation foundations - animate*AsState, AnimatedVisibility, Crossfade, updateTransition, SharedTransitionLayout, gestures." metadata: internal: true
Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in
_jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.
Compose Motion - Sub-skill
Jetpack Compose animation core. Loaded for Android Compose and Compose Multiplatform projects. Concise rules here. Deep-dive in
references/. Baseline: Jetpack Compose 1.12.0 (current stable, Aug 2026) - i.e. Compose BOM 2026.08.x. Modern stable APIs only - noswipeable, noanimateContentSizehacks whereAnimatedContentis correct. Anything below flagged 'Compose 1.x+' is a since marker, not the target.
API Decision Tree
| Need | API |
|---|---|
| Single value over time | animateFloatAsState, animateDpAsState, animateColorAsState, etc. |
| Visibility / mount-unmount | AnimatedVisibility(visible) { ... } |
| Crossfade between states | Crossfade(target) { state -> ... } |
| Multi-state coordinated | updateTransition(target).animateFloat { ... } |
| Manual control / interruption | Animatable(initialValue) + animateTo(...) |
| Looping / infinite | rememberInfiniteTransition().animateFloat(...) |
| Shared elements | SharedTransitionLayout + Modifier.sharedElement(...) (Compose 1.7+) |
| Layout swap with anim | AnimatedContent(target) { ... } |
| Drag / swipe | Modifier.draggable + Animatable.snapTo/animateTo, or Modifier.anchoredDraggable for snap-points |
Rule: climb the ladder only when needed. animate*AsState covers 70% of cases. Reach for Animatable only when you need to interrupt, chain, or read velocity.
Spring API (opinionated defaults)
| Use | Spec |
|---|---|
| UI snap (modal, drawer, tab) | spring(stiffness = Spring.StiffnessMediumLow, dampingRatio = Spring.DampingRatioNoBouncy) |
| Tactile (button press release, toggle) | spring(stiffness = Spring.StiffnessMedium, dampingRatio = 0.85f) |
| Bouncy reveal (toast, FAB, success) | spring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy) |
| Drag follow (1:1 finger tracking) | spring(stiffness = Spring.StiffnessHigh, dampingRatio = 1f) |
Stiffness constants (androidx.compose.animation.core.Spring): StiffnessVeryLow 50f, StiffnessLow 200f, StiffnessMediumLow 400f, StiffnessMedium 1500f (the spring() default), StiffnessHigh 10000f. Higher = faster settle. Damping constants: DampingRatioHighBouncy 0.2f, DampingRatioMediumBouncy 0.5f, DampingRatioLowBouncy 0.75f, DampingRatioNoBouncy 1.0f (the default). Below 1.0 overshoots. Springs ignore durationMillis; if you need a deterministic duration, use tween(...) instead.
animate*AsState - The Bread and Butter
val targetAlpha = if (visible) 1f else 0f
val alpha by animateFloatAsState(
targetValue = targetAlpha,
animationSpec = spring(stiffness = Spring.StiffnessMedium),
label = "alpha",
)
Box(modifier = Modifier.alpha(alpha))
The label shows up in Layout Inspector / Animation Preview - always set it, future-you will thank present-you. Variants ship for Dp, Color, Offset, IntOffset, Size, Rect, Float, Int, and a generic animateValueAsState for custom types via TwoWayConverter.
AnimatedVisibility - Mount / Unmount with Anim
AnimatedVisibility(
visible = expanded,
enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut(animationSpec = tween(150)),
) {
Panel()
}
Combine multiple enter/exit transitions with +. Respect motion-principles: exit shorter and simpler than enter (here 150ms tween fade vs full slide+fade enter). The content composable only runs while visible OR animating - safe to mount expensive children inside.
AnimatedContent - State-Driven Layout Swap
AnimatedContent(
targetState = currentTab,
transitionSpec = {
(slideInHorizontally { it } + fadeIn()) togetherWith
(slideOutHorizontally { -it } + fadeOut())
},
label = "tabs",
) { tab ->
TabContent(tab)
}
togetherWith runs enter and exit in parallel; using SizeTransform(clip = false) controls how the container resizes between contents. Keys matter: if targetState doesn't change identity, no transition fires.
Crossfade - Simple Fade Between States
Crossfade(targetState = isLoading, label = "loadState") { loading ->
if (loading) Spinner() else Content()
}
Use when you only need a fade. For anything richer (slide, scale, layout-aware), reach for AnimatedContent. Crossfade does NOT animate size - the container takes the size of the new content immediately.
updateTransition - Multi-Property Coordinated
val transition = updateTransition(targetState = expanded, label = "expand")
val width by transition.animateDp(label = "width") { if (it) 300.dp else 100.dp }
val color by transition.animateColor(label = "color") { if (it) Color.Blue else Color.Gray }
val corner by transition.animateDp(label = "corner") { if (it) 24.dp else 8.dp }
Box(
Modifier
.width(width)
.background(color, RoundedCornerShape(corner)),
)
Use when several properties animate together based on the same state. All children share the same transition timeline, so they finish in sync. Each animate* call accepts its own transitionSpec lambda for per-property tuning.
Animatable - Manual Control
val offsetX = remember { Animatable(0f) }
LaunchedEffect(triggerEvent) {
offsetX.animateTo(100f, spring())
offsetX.animateTo(0f, spring(dampingRatio = Spring.DampingRatioMediumBouncy))
}
Box(Modifier.offset { IntOffset(offsetX.value.roundToInt(), 0) })
Reach for Animatable when you need to interrupt (stop()), chain (animateTo returns when finished), read live velocity, or kick off decay (animateDecay). It is the imperative escape hatch for drag-then-fling, snap-back, and any flow animate*AsState cannot express.
SharedTransitionLayout (Compose 1.7+) - Hero Animations
SharedTransitionLayout {
AnimatedContent(targetState = currentScreen, label = "nav") { screen ->
when (screen) {
Screen.List -> ListScreen(
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@AnimatedContent,
)
is Screen.Detail -> DetailScreen(
item = screen.item,
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@AnimatedContent,
)
}
}
}
// Inside ListScreen, on the card image:
with(sharedTransitionScope) {
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.sharedElement(
sharedContentState = rememberSharedContentState(key = "hero-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope,
),
)
}
Two scopes plumbed down: SharedTransitionScope (where Modifier.sharedElement extension lives) and AnimatedVisibilityScope (the visibility context that drives the transition). rememberSharedContentState(key) keys MUST match across screens or no animation fires. Deep-dive in references/shared-transitions.md.
InfiniteTransition - Looping
val infinite = rememberInfiniteTransition(label = "loader")
val rotation by infinite.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = LinearEasing),
repeatMode = RepeatMode.Restart,
),
label = "rotation",
)
Icon(Icons.Default.Refresh, contentDescription = null, modifier = Modifier.rotate(rotation))
RepeatMode.Restart jumps back to start each cycle; RepeatMode.Reverse ping-pongs. Always provide a pause / off path for accessibility (see Reduced Motion section below). For one-shot decay (fling), use Animatable.animateDecay instead.
Gestures (quick map)
| Need | Modifier |
|---|---|
| Tap | Modifier.clickable(onClick = ...) |
| Tap + long-press | Modifier.combinedClickable(onClick = ..., onLongClick = ...) |
| Drag (single axis) | Modifier.draggable(state, orientation) |
| Multi-touch / custom | Modifier.pointerInput { detectDragGestures { ... } } |
| Scroll offset reading | LazyListState or ScrollState.value |
| Swipe-to-dismiss / snap points | Modifier.anchoredDraggable(state) (Compose 1.6+, replaces swipeable) |
| Pinch + pan + rotate | Modifier.transformable(state) |
Deep-dive in references/gestures-compose.md (NestedScrollConnection, conflict resolution, fling decay).
Anti-Patterns (BAD / GOOD)
1. Animating layout size instead of transform
// BAD - animateDpAsState on width forces a layout pass every frame
val w by animateDpAsState(if (expanded) 300.dp else 100.dp)
Box(Modifier.width(w).height(60.dp))
// GOOD - animate scale via graphicsLayer (composite-only, no layout)
val scale by animateFloatAsState(if (expanded) 3f else 1f, label = "scale")
Box(
Modifier
.width(100.dp)
.height(60.dp)
.graphicsLayer {
scaleX = scale
transformOrigin = TransformOrigin(0f, 0.5f)
},
)
If the actual layout size MUST change (parent depends on it), use Modifier.animateContentSize() so neighbors animate too, or wrap the swap in AnimatedContent.
2. LaunchedEffect(true) / LaunchedEffect(Unit) with hidden inputs
// BAD - re-runs only once, BUT also fires on every recomposition surprise
// when callers swap the composable instance. Worse: anything captured
// in the lambda is stale.
LaunchedEffect(true) {
offsetX.animateTo(target)
}
// GOOD - explicit key tied to the trigger
LaunchedEffect(triggerKey) {
offsetX.animateTo(target)
}
If you genuinely want "run once", use LaunchedEffect(Unit) on purpose AND ensure you don't capture varying state - or hoist the captured values out. When in doubt, key on the values you read.
3. Forgetting key in a LazyColumn with item animations
// BAD - on insert/delete/reorder, items animate to wrong slots
LazyColumn {
items(list) { item ->
AnimatedVisibility(visible = item.expanded) { ItemRow(item) }
}
}
// GOOD - stable key by id, plus Modifier.animateItem for reorder anim
LazyColumn {
items(list, key = { it.id }) { item ->
Row(modifier = Modifier.animateItem()) {
AnimatedVisibility(visible = item.expanded) { ItemRow(item) }
}
}
}
Modifier.animateItem() (Compose 1.7+, replaces animateItemPlacement) handles insert/remove/move automatically when key is stable.
4. Nesting AnimatedContent inside scrolling list items
// BAD - every item runs its own transition graph; scroll = jank
LazyColumn {
items(list, key = { it.id }) { item ->
AnimatedContent(targetState = item.state) { state -> Row(state) }
}
}
// GOOD - lift state, animate only the changing prop on the row
LazyColumn {
items(list, key = { it.id }) { item ->
val color by animateColorAsState(if (item.selected) selBg else bg, label = "rowBg")
Row(Modifier.background(color)) { Content(item) }
}
}
Rule: heavy animation containers (AnimatedContent, SharedTransitionLayout) belong at screen scope, not per row.
Reduced Motion - Respect It
See ../motion-principles/SKILL.md for the cross-platform doctrine.
@Composable
fun rememberReduceMotion(): Boolean {
val
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
90.8kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
74.4kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
Scrapling
85.7k🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
crawl4ai
84.8kOpen-source web crawler and scraper for LLMs and AI agents: any website into clean, LLM-ready Markdown. Run it yourself, or use Crawl4AI Cloud with one key.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
