Sitemap
Nisarg Ratani

Nisarg Ratani

Flutter & Node.js developer with 6+ years of experience. Published few articles on Medium, sharing practical insights on Flutter, Dart, and Node.js.

Flutter Hero Animation: Creating Seamless Screen-to-Screen Transitions

5 min readOct 8, 2026

--

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-123

Flutter 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: index

because list ordering can change.

Hero Flight

The animation between the two routes is often called the Hero flight.

Conceptually:

Source Hero
↓
Hero flight
↓
Destination Hero

Flutter 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:

flightShuttleBuilder

This 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 transition

For 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
└── Settings

If 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
└── Product
Favorites
└── Product

Hero 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:

image

This 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:
CircleAvatar
Destination:
Large rectangular image

Flutter 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 Screen

You can customize the route transition independently.

This allows combinations such as:

Hero image
+
Fade route transition

or:

Hero image
+
Slide route transition

Production Example

A common product flow:

Product Grid
↓
Tap product
↓
Product image Hero transition
↓
Product Details

Source:

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 transition

Conclusion

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
HeroController

Hero animations work particularly well for:

Product images
Profile avatars
Gallery images
Article cards
Media previews

When 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.

--

--

Nisarg Ratani
Nisarg Ratani