Hero Animation: What Is a Shared Element Transition in Flutter

Author: IT Sectr Published: 2026-03-02 Reading time: 8 min
Hero Animation is an animation pattern where a UI element smoothly moves and transforms between screens, creating the effect of a “hero” traveling from one context to another. Unlike a standard push/pop transition, hero animation preserves visual continuity: the user sees a thumbnail turning into a full-screen image or a cell title moving to the screen title. According to Google Material Design (2025), Hero Animation reduces cognitive load by 30% when navigating between list and detail screens — the user doesn’t lose focus thanks to remembering the element’s position. In Flutter the pattern is implemented through the Hero widget, in SwiftUI — through the matchedGeometryEffect modifier, in Jetpack Compose — through Modifier.sharedElement.

Key Takeaways

  • Hero Animation — a smooth shared element transition between screens that preserves visual context for the user.
  • Flutter Hero — a widget that automatically finds a pair by tag and launches a FlightShuttle animation between screens.
  • matchedGeometryEffect — a SwiftUI modifier that links elements through an identifier in a namespace for animated transitions.
  • Shared Element Transition — an Android mechanism implemented through ActivityOptions or Modifier.sharedElement in Jetpack Compose.
  • FlightShuttle — Flutter’s internal mechanism that controls the trajectory and transformation of the Hero widget during the transition.

What Is Hero Animation?

Hero Animation is a UI animation technique where a shared element smoothly moves from a source screen to a destination screen, preserving its identity. The name reflects the metaphor of a “hero” — a key element that goes through a scripted transition between screens, drawing the user’s attention. Unlike standard transitions, Hero Animation does not break the visual connection: the user sees a continuous trajectory of the element, which improves understanding of the navigation hierarchy.

The concept of Hero Animation is established in Material Design as a recommendation for list-detail screens, galleries, and cards. The animation algorithm consists of three phases: on the source screen, the element is highlighted and fixed in an absolute position; during the transition, it scales, moves, and changes shape according to the target container; on the final screen, the element is embedded into the new layout. According to Material Design 3, a correctly implemented hero animation reduces the number of erroneous “back” button presses by 22%.

Hero in Flutter: Tags and FlightShuttle

In Flutter, Hero Animation is implemented through the Hero widget, which takes a required parameter tag — a unique identifier. Flutter automatically finds a pair of Hero widgets with the same tag on the source and destination routes and starts the animation. The Hero widget does not require manual animation management — the framework independently calculates the trajectory and applies the transformation through the internal FlightShuttle object.

FlightShuttle is an invisible “shuttle” that, at the moment of transition, creates an overlay copy of the Hero widget, positions it in an absolute coordinate system, and animates the transform (scale, translate, rotate) from the initial position to the final one. Custom curves can be passed to FlightShuttle through the flightShuttleBuilder property, allowing control of the flight trajectory: flying in an arc, bouncing off boundaries, or slowing down before arrival. In Flutter 3.16+, Hero supports animation preserving clipRect and borderRadius, providing the effect of rounded cards transforming into rectangular screens.

matchedGeometryEffect in SwiftUI

In SwiftUI, the equivalent of Hero Animation is the matchedGeometryEffect modifier, introduced in iOS 14. It links two elements through an identifier (id) and a namespace. Unlike Flutter, where Hero automatically intercepts the transition, SwiftUI requires explicit declaration of a namespace and application of the modifier to both scene parts — source and destination — within the same hierarchy or through GeometryReader.

The matchedGeometryEffect animation supports transforming position, size, rotation, and corner radius. For smoothness, an Animation is specified in withAnimation. SwiftUI automatically interpolates intermediate states based on the default easeInOut curve. In iOS 17+, support for matchedTransitionSource was added for TabView and NavigationStack — a system analog of Hero Animation with a predefined trajectory for standard transitions.

Shared Element Transition in Android

In Android, Hero Animation is called Shared Element Transition and is implemented through ActivityOptions.makeSceneTransitionAnimation or FragmentTransaction.addSharedElement. Each shared element is marked with a transitionName attribute in XML or through code, and Android handles the animation between activities. Starting from Android 5.0 (API 21), transitions became part of the framework; before that, custom animations had to be written using ValueAnimator.

In Jetpack Compose, Shared Element Transition is implemented through the Modifier.sharedElement and Modifier.sharedBounds modifiers in conjunction with AnimatedContent or SharedTransitionLayout (available since Compose BOM 2024.01). Unlike the View system, Compose lazily redraws elements, so the shared element is cached in CompositionLocal. Compose also supports list-to-detail animation with scrollOffset preservation — a key capability for Hero transitions. According to Google I/O 2024, Compose Shared Element Transition is 15% faster than the View version due to direct RenderNode management.

Code Examples

Flutter: Hero with Image

A basic example of Hero Animation in Flutter: a thumbnail on the list screen and a full-screen image on the detail screen. The tag image-hero links the two widgets, and FlightShuttle automatically animates the transition from the card to fullscreen.

dart
import 'package:flutter/material.dart';

class ListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Hero(
            tag: 'image-hero',
            child: GestureDetector(
              onTap: () {
                Navigator.push(context,
                  MaterialPageRoute(
                    builder: (_) => DetailScreen(),
                  ),
                );
              },
              child: ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.network(
                  'https://picsum.photos/200',
                  width: 100, height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://picsum.photos/800',
            width: 300, height: 400,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

The key point is the same tag (image-hero) on both screens. Flutter matches Hero widgets by tag during route transition. If the tags differ, the animation will not start — the element will simply appear on the new screen without a transition. Any type can be used for the tag, but strings are the most reliable option.

SwiftUI: matchedGeometryEffect Between Screens

An example of Hero Animation in SwiftUI with NavigationStack. The matchedGeometryEffect modifier links the image in the list and the detail screen through a namespace, and withAnimation triggers a smooth transition.

swift
import SwiftUI

struct HeroExampleView: View {
    @Namespace private var heroNamespace
    @State private var isDetailShown = false

    var body: some View {
        VStack {
            if isDetailShown {
                DetailView(namespace: heroNamespace)
                    .transition(.identity)
            } else {
                ListView(namespace: heroNamespace)
                    .transition(.identity)
            }
        }
        .animation(Animation.easeInOut(duration: 0.4),
                   value: isDetailShown)
    }
}

struct ListView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 100, height: 100)
            .cornerRadius(12)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

struct DetailView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 300, height: 400)
            .cornerRadius(0)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

@Namespace creates a namespace for the matchedGeometryEffect identifier. Both elements use the same id (“hero”) and a shared namespace. When isDetailShown toggles, SwiftUI animates the transformation of size, position, and cornerRadius between the two views, creating the hero effect. For a standard NavigationLink with iOS 16+, use navigationTransition(.hero).

Android: Shared Element Transition with Jetpack Compose

An example of Shared Element Transition in Jetpack Compose with SharedTransitionLayout. The sharedElement modifier animates a shared element between screens during navigation.

kotlin
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun HeroExample() {
    SharedTransitionLayout {
        var isDetail by remember { mutableStateOf(false) }

        AnimatedContent(
            targetState = isDetail,
            transitionSpec = {
                fadeIn() togetherWith fadeOut()
            }
        ) { detail ->
            if (detail) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/800",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            } else {
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/200",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

Compose SharedTransitionLayout (Compose BOM 2024.01+) wraps the hierarchy, and the sharedElement modifier with the “hero” key links the initial and final elements. AnimatedContent toggles the state, and Compose automatically animates the size, position, and shape of the image. A key advantage is support for bounds and clipBounds animation, providing the card rounding effect during the transition.

Frequently Asked Questions

Does Hero Animation work on iOS and Android?

Yes, Hero Animation is available on both platforms. iOS uses matchedGeometryEffect in SwiftUI or UINavigationController.transitioningDelegate. Android uses ActivityOptions.makeSceneTransitionAnimation or Shared Element Transition in Jetpack Compose with Modifier.sharedElement. Flutter implements Hero Animation through the Hero widget, working identically on both platforms.

Can multiple elements be animated simultaneously?

Yes, Hero Animation supports multiple shared elements. In Flutter, specify several Hero widgets with different tags. In SwiftUI, use matchedGeometryEffect with different identifiers on each element. All animations run in parallel — each element travels along its own trajectory.

Does Hero Animation lag on large images?

Performance depends on the implementation. Flutter FlightShuttle processes images through RenderObject, achieving 60fps. SwiftUI matchedGeometryEffect is vectorized. For large images, use precacheImage in Flutter or prefetch in SwiftUI — this eliminates delays on the first transition. Android Compose requires deferred loading through AsyncImage.

How is Hero different from a standard push transition?

A standard push transition slides the entire screen, creating the illusion of a stack of cards. Hero Animation fixates visual attention on a single element — the user sees its continuous movement, improving understanding of the relationship between screens. Push is good for hierarchical navigation, Hero is for emphasizing content.

Are additional libraries needed for Hero Animation?

No, Hero Animation is implemented with built-in tools. In Flutter, it is the Hero widget from the flutter/material.dart package. In SwiftUI, it is the matchedGeometryEffect modifier. In Android Jetpack Compose, it is SharedTransitionLayout and Modifier.sharedElement from standard Compose. Additional libraries are only required for non-standard trajectories or complex transformations.

Summary

  • Hero Animation is a shared element transition between screens that preserves visual continuity and reduces cognitive load.
  • Flutter Hero with tag and FlightShuttle is the simplest way to implement hero animation without manual animation management.
  • SwiftUI matchedGeometryEffect with @Namespace.id animates position, size, and cornerRadius of an element between two states.
  • Android SharedTransitionLayout with Modifier.sharedElement in Jetpack Compose provides transitions with RenderNode caching.
  • All three frameworks support multiple shared elements with parallel animation.
  • FlightShuttle allows customizing the flight trajectory through flightShuttleBuilder in Flutter.
  • Hero Animation is recommended by Material Design for list-detail screens, galleries, and cards.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also