transition:crossfade
Create smooth shared element transitions between screens with matching keys.
Overview
Shared element transitions create visual continuity as you navigate between screens. An element on one screen morphs into the corresponding element on another screen.
Basic Usage
Add the same transition:crossfade key to elements on both screens:
List Screen
<LazyColumn>
@for (movie in movies) {
<Card onClick={() => $navigate("/movie/{movie.id}")}>
<Image
src={movie.poster}
transition:crossfade="poster-{movie.id}"
/>
<Text>{movie.title}</Text>
</Card>
}
</LazyColumn> Detail Screen
<Column>
<Image
src={$data.posterUrl}
transition:crossfade="poster-{$route.params.id}"
/>
<Text>{$data.title}</Text>
<Text>{$data.description}</Text>
</Column> The crossfade key must be identical on both screens. Use dynamic values like IDs to ensure each item has a unique, matching key.
Configuration
Customize the transition with an options object:
<Image
transition:crossfade={{
key: "poster-{id}",
duration: 400,
type: "bounds"
}}
/> Options
| Option | Type | Default | Description |
|---|---|---|---|
key | String | Required | Unique identifier to match elements |
type | String | "element" | "element" or "bounds" - animation style |
duration | Number | 300 | Animation duration in milliseconds |
Transition Types
"element"- Animates the element itself (recommended for images, icons)"bounds"- Animates only position and size (recommended for containers)
Asymmetric Transitions
Use different durations for enter and exit:
<Image
in:crossfade={{ key: "poster-{id}", duration: 400 }}
out:crossfade={{ key: "poster-{id}", duration: 150 }}
/> Examples
Product Gallery
// Products list
<LazyVerticalGrid columns={2}>
@for (product in products) {
<Card onClick={() => $navigate("/product/{product.id}")}>
<Image
src={product.image}
transition:crossfade="product-{product.id}"
/>
<Text>{product.name}</Text>
</Card>
}
</LazyVerticalGrid>
// Product detail
<Column>
<Image
src={$data.image}
transition:crossfade="product-{$route.params.id}"
h={400}
/>
<Text fontSize={24}>{$data.name}</Text>
<Text>{$data.description}</Text>
</Column> User Avatar
// User list
@for (user in users) {
<Row onClick={() => $navigate("/user/{user.id}")}>
<Image
src={user.avatar}
transition:crossfade="avatar-{user.id}"
w={48} h={48}
cornerRadius={24}
/>
<Text>{user.name}</Text>
</Row>
}
// User profile
<Column>
<Image
src={$data.avatar}
transition:crossfade="avatar-{$route.params.id}"
w={120} h={120}
cornerRadius={60}
/>
<Text fontSize={32}>{$data.name}</Text>
</Column> Multiple Shared Elements
Animate multiple elements on the same screen pair:
// Article list
<Card onClick={() => $navigate("/article/{article.id}")}>
<Image
src={article.cover}
transition:crossfade="cover-{article.id}"
/>
<Text
transition:crossfade="title-{article.id}"
>
{article.title}
</Text>
</Card>
// Article detail
<Column>
<Image
src={$data.cover}
transition:crossfade="cover-{$route.params.id}"
h={300}
/>
<Text
fontSize={28}
transition:crossfade="title-{$route.params.id}"
>
{$data.title}
</Text>
<Text>{$data.body}</Text>
</Column> With Navigation State
Pass preview data for instant rendering:
// List (pass preview data)
<Card onClick={() => $navigate(
"/show/{show.id}",
state = { poster: show.poster, title: show.name }
)}>
<Image
src={show.poster}
transition:crossfade="poster-{show.id}"
/>
</Card>
// Detail (use preview until loaded)
suspend fun load(): ShowData {
val preview = $route.state
val show = $fetch.get(url = "...")
return ShowData(show)
}
<Image
src={$route.state?.poster ?: $data.posterUrl}
transition:crossfade="poster-{$route.params.id}"
/> Best Practices
- Use descriptive, unique keys:
"poster-{id}", not just"{id}" - Keep duration short (300-400ms) for snappy feel
- Use
type="element"for images,type="bounds"for containers - Combine with navigation state for smooth loading states
- Limit to 1-3 shared elements per screen transition
Shared element transitions work with Compose's predictive back gesture. Users can scrub the animation by dragging the back gesture.
See Also
- Transitions - Enter/exit animations
- Navigation - $navigate and routing
- Data Loading - Screen loaders and state