Flutter Hero Animation: Creating Seamless Screen-to-Screen Transitions
Imagine tapping a product image on a shopping app.
Instead of the image disappearing and a completely new screen appearing, the image smoothly moves from the product card into the product details page.
That transition is called a Hero animation.
Flutter provides the Hero widget to make these shared-element transitions relatively simple.
In this article, we’ll understand how Hero animations work and how to use them effectively in production applications.
What Is a Hero Animation?
A Hero animation visually connects a widget on one screen with a corresponding widget on another screen.
For example:
Screen A┌─────────────┐
│ Product │
│ Image │
└─────────────┘
│
│ transition
▼
┌─────────────────────┐
│ │
│ Product Image │
│ │
│ Product Name │
│ │
└─────────────────────┘Screen B
The shared element appears to travel between the two screens.
Basic Hero Example
On the first screen:
Hero(
tag: 'product-image-1',
child: Image.network(
imageUrl,
),
)On the second screen:
Hero(
tag: 'product-image-1',
child: Image.network(
imageUrl,
),
)The important part is:
tag: 'product-image-1'Both Hero widgets must have matching tags within the relevant navigation context.
Complete Example
Product List
class ProductCard extends StatelessWidget {
const ProductCard({
required this.product,
super.key,
});
final Product product;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) {
return ProductDetailsPage(
product: product,
);
},
),
);
},
child: Hero(
tag: 'product-${product.id}',
child: Image.network(
product.imageUrl,
),
),
);
}
}Details Screen
class ProductDetailsPage extends StatelessWidget {
const ProductDetailsPage({
required this.product,
super.key,
});
final Product product;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product'),
),
body: Hero(
tag: 'product-${product.id}',
child: Image.network(
product.imageUrl,
),
),
);
}
}Because the tags match:
product-123Flutter can create the Hero transition.
Why the Tag Matters
The tag identifies the shared element.
For example:
Hero(
tag: product.id,
child: ...
)is often better than:
Hero(
tag: 'image',
child: ...
)if your screen contains multiple products.
Every relevant Hero tag must be unique within the appropriate route subtree.
Use Stable Tags
For dynamic content, derive the tag from a stable identifier.
For example:
tag: 'product-${product.id}'This is safer than using:
tag: indexbecause list ordering can change.
Hero Flight
The animation between the two routes is often called the Hero flight.
Conceptually:
Source Hero
↓
Hero flight
↓
Destination HeroFlutter creates the transition during the route change.
Hero and Images
Hero animations are particularly useful for:
- Product images
- Profile avatars
- Gallery images
- Articles
- Cards
- Media previews
Example:
Hero(
tag: 'profile-avatar',
child: CircleAvatar(
backgroundImage: NetworkImage(
imageUrl,
),
),
)On the next screen:
Hero(
tag: 'profile-avatar',
child: CircleAvatar(
radius: 80,
backgroundImage: NetworkImage(
imageUrl,
),
),
)The avatar can smoothly transition between sizes.
Customizing the Hero Flight
Flutter provides:
flightShuttleBuilderThis lets you customize the widget used during the Hero transition.
Example:
Hero(
tag: 'product-image',
flightShuttleBuilder: (
flightContext,
animation,
flightDirection,
fromHeroContext,
toHeroContext,
) {
return const Icon(
Icons.shopping_bag,
size: 100,
);
},
child: const Icon(
Icons.shopping_bag,
),
)This is useful for advanced transitions.
Placeholder Builder
You can also customize what appears in the original location during the transition.
Hero(
tag: 'product-image',
placeholderBuilder: (
context,
size,
child,
) {
return SizedBox(
width: size.width,
height: size.height,
);
},
child: image,
)HeroController
Flutter’s Navigator uses a Hero controller to coordinate Hero animations.
For most applications, you don’t need to manage this manually.
However, understanding this becomes useful when working with:
- Nested navigators
- Custom navigation
- Multiple navigation stacks
- Advanced routing
Hero With GoRouter
Hero animations can also work with declarative routing systems such as go_router.
The key requirement remains the same:
Matching Hero tags
+
Routes participating in the transitionFor example:
Hero(
tag: 'product-${product.id}',
child: ProductImage(...),
)The destination route should contain the same tag.
Hero and Nested Navigation
Nested navigation introduces additional complexity because Hero animations are coordinated within navigation contexts.
Get Nisarg Ratani’s stories in your inbox
Join Medium for free to get updates from this writer.
For example:
Root Navigator
├── Home Navigator
│ ├── Home
│ └── Details
│
└── Profile Navigator
├── Profile
└── SettingsIf the source and destination belong to different navigators, you may need to explicitly configure which navigator participates in the Hero transition.
This becomes especially important with:
- Bottom navigation
- Shell routes
- Independent navigation stacks
Hero Across Bottom Navigation
Suppose each tab maintains its own navigation stack.
Home
└── ProductFavorites
└── ProductHero transitions generally need to occur within the correct navigation context.
Don’t assume that a matching tag automatically means every Hero can transition across every navigator.
Common Hero Error
A common error is caused by duplicate Hero tags.
For example:
ListView(
children: [
Hero(
tag: 'image',
child: Image.network(url1),
),
Hero(
tag: 'image',
child: Image.network(url2),
),
],
)Both widgets use:
imageThis can cause a Hero tag conflict.
Instead:
Hero(
tag: 'image-${product.id}',
child: ...
)Hero and ListView
When using Hero animations inside lists, use stable identifiers.
For example:
Hero(
tag: 'product-${product.id}',
child: ProductImage(
product: product,
),
)Avoid using list indexes as Hero identifiers.
Hero With Different Widget Types
The source and destination don’t necessarily have to look exactly identical.
For example:
Source:
CircleAvatarDestination:
Large rectangular imageFlutter can animate the shared widget between the two routes.
However, the transition can look better when the geometry and visual properties change in a controlled way.
Hero and Route Transitions
Hero animations run alongside route transitions.
For example:
Source Screen
↓
Hero flight + Route transition
↓
Destination ScreenYou can customize the route transition independently.
This allows combinations such as:
Hero image
+
Fade route transitionor:
Hero image
+
Slide route transitionProduction Example
A common product flow:
Product Grid
↓
Tap product
↓
Product image Hero transition
↓
Product DetailsSource:
Hero(
tag: 'product-${product.id}',
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
),
)Destination:
Hero(
tag: 'product-${product.id}',
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
)This small addition can make navigation feel significantly more connected.
Hero Performance
Hero animations can involve expensive widgets.
Be careful with:
- Large images
- Blur effects
- Complex custom painters
- Large shadows
- Heavy widget trees
Keep the Hero child as simple as practical.
Accessibility
A Hero animation should enhance the transition, not be required to understand the application.
Important information should remain available without relying on animation.
Consider reduced-motion preferences for non-essential motion.
Common Mistakes
1. Duplicate Tags
Every relevant Hero must have an appropriate unique tag.
2. Unstable Tags
Avoid using list indexes.
3. Forgetting the Destination Hero
A Hero transition requires matching Hero participants.
4. Complex Hero Children
Large or expensive widgets can make transitions less smooth.
5. Ignoring Nested Navigators
Nested navigation can change which Hero controllers participate in a transition.
Hero Animation Checklist
Before using Hero, verify:
✓ Source Hero exists
✓ Destination Hero exists
✓ Tags match
✓ Tags are stable
✓ Tags are unique where required
✓ Navigator context is correct
✓ Hero child is reasonably lightweight
✓ Animation improves the transitionConclusion
Hero animations are one of Flutter’s most useful tools for creating connected navigation experiences.
The basic implementation is surprisingly simple:
Hero(
tag: 'unique-id',
child: YourWidget(),
)Place matching Hero widgets on the source and destination routes, and Flutter handles the transition.
For advanced scenarios, Flutter also provides:
flightShuttleBuilder
placeholderBuilder
HeroControllerHero animations work particularly well for:
Product images
Profile avatars
Gallery images
Article cards
Media previewsWhen combined with the animation techniques covered in the previous articles, Hero animations become another powerful tool for creating polished Flutter interfaces.
Next, we’ll move beyond built-in widgets and learn how to draw completely custom UI with CustomPainter and Flutter’s Canvas API.






