image

26 Jan 2026

9K

35K

Implementing Fade & Scale Animations for Flutter Modal Dialogs

Modal dialogs are crucial UI elements for user interaction in applications. While functional, a sudden appearance and disappearance can feel jarring. Introducing subtle animations, such as fade and scale, significantly enhances the user experience by providing visual cues and making transitions feel smoother and more natural.

Why Animate Modal Dialogs?

Animations guide the user's attention, indicate state changes, and add a polished feel to an application. For modal dialogs, a fade-in effect gently introduces the new content, while a scale effect provides a sense of the dialog "emerging" or "growing" into view, making the interaction more intuitive and less abrupt. This approach transforms a basic interaction into a more engaging and professional one.

Core Concepts for Custom Dialog Animations

Flutter provides powerful tools for building custom animations. For modal dialogs, the primary components we'll leverage are:

  • PageRouteBuilder: This class allows us to define a custom route transition, giving us full control over how a new page (or in our case, a dialog that behaves like a page) appears and disappears.
  • AnimationController: Manages the animation's state, including its duration and direction. While PageRouteBuilder typically handles this internally via the animation and secondaryAnimation objects, understanding its role is key.
  • FadeTransition: An animated widget that fades its child in and out. It requires an Animation for its opacity.
  • ScaleTransition: An animated widget that scales its child. It requires an Animation for its scale factor.
  • Curves: Used to define the non-linear motion of an animation, such as Curves.easeOutBack or Curves.fastOutSlowIn, adding a more natural feel.

Implementing the Fade & Scale Dialog Animation

To achieve a custom fade and scale animation for a modal dialog, we'll create a custom PageRoute that wraps our dialog content.

Step 1: Define a Custom Dialog Route

First, let's create a function that returns a PageRouteBuilder. This builder will define our transition logic.


PageRouteBuilder createFadeScaleDialogRoute(Widget dialogContent) {
  return PageRouteBuilder(
    opaque: false, // Make the route transparent to show the underlying content
    pageBuilder: (context, animation, secondaryAnimation) => dialogContent,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      // Define the scale animation
      var scaleTween = Tween(begin: 0.8, end: 1.0);
      var scaleAnimation = CurvedAnimation(
        parent: animation,
        curve: Curves.easeOutBack, // Or Curves.easeOutCubic, etc.
      );

      // Define the fade animation
      var fadeTween = Tween(begin: 0.0, end: 1.0);
      var fadeAnimation = CurvedAnimation(
        parent: animation,
        curve: Curves.easeIn,
      );

      return FadeTransition(
        opacity: fadeTween.animate(fadeAnimation),
        child: ScaleTransition(
          scale: scaleTween.animate(scaleAnimation),
          child: child,
        ),
      );
    },
    transitionDuration: const Duration(milliseconds: 700), // Adjust duration
    reverseTransitionDuration: const Duration(milliseconds: 400),
  );
}

Explanation of the Route Builder:

  • opaque: false: Essential for modal dialogs, allowing the underlying route to remain visible.
  • pageBuilder: Simply returns our dialogContent. The actual animation logic is in transitionsBuilder.
  • transitionsBuilder: This is where the animation magic happens. It provides an animation object (for forward transitions) and secondaryAnimation (for reverse transitions).
    • We create a Tween for both scale and fade, defining the start and end values.
    • CurvedAnimation is used to apply a non-linear curve to the animation, making it more dynamic. Curves.easeOutBack gives a slight "overshoot" effect, which is appealing for dialogs.
    • We compose FadeTransition and ScaleTransition, with ScaleTransition being the child of FadeTransition, allowing both animations to apply to the dialog content.
  • transitionDuration and reverseTransitionDuration: Control how long the animation takes to play forward and reverse, respectively.

Step 2: Create Your Modal Dialog Content

This is a standard Flutter widget that represents your dialog's UI. It should be wrapped appropriately to ensure it appears as a centered dialog.


Widget _buildAnimatedDialogContent(BuildContext context) {
  return Center(
    child: Material(
      color: Colors.transparent, // Make Material transparent
      child: Container(
        padding: const EdgeInsets.all(24.0),
        margin: const EdgeInsets.all(32.0),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(16.0),
          boxShadow: const [
            BoxShadow(
              color: Colors.black26,
              blurRadius: 10.0,
              offset: Offset(0, 4),
            ),
          ],
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text(
              'Animated Dialog Title',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            const Text(
              'This is a custom modal dialog with a beautiful fade and scale animation!',
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: const Text('Close'),
            ),
          ],
        ),
      ),
    ),
  );
}

Note that we wrap the dialog content in Center and Material with a transparent color. This is important because PageRouteBuilder creates a full-screen overlay, and we want our dialog to be centered and have its own background without the default Material background of a standard showDialog call.

Step 3: Show the Animated Dialog

Now, to display your animated dialog, you use Navigator.push with your custom route.


void _showCustomAnimatedDialog(BuildContext context) {
  Navigator.of(context).push(
    createFadeScaleDialogRoute(
      _buildAnimatedDialogContent(context),
    ),
  );
}

Integrating into a Flutter App

Here's how you might put it all together in a simple Flutter application:


import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Animated Dialog',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  PageRouteBuilder _createFadeScaleDialogRoute(Widget dialogContent) {
    return PageRouteBuilder(
      opaque: false,
      pageBuilder: (context, animation, secondaryAnimation) => dialogContent,
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        var scaleTween = Tween(begin: 0.8, end: 1.0);
        var scaleAnimation = CurvedAnimation(
          parent: animation,
          curve: Curves.easeOutBack,
        );

        var fadeTween = Tween(begin: 0.0, end: 1.0);
        var fadeAnimation = CurvedAnimation(
          parent: animation,
          curve: Curves.easeIn,
        );

        return FadeTransition(
          opacity: fadeTween.animate(fadeAnimation),
          child: ScaleTransition(
            scale: scaleTween.animate(scaleAnimation),
            child: child,
          ),
        );
      },
      transitionDuration: const Duration(milliseconds: 700),
      reverseTransitionDuration: const Duration(milliseconds: 400),
    );
  }

  Widget _buildAnimatedDialogContent(BuildContext context) {
    return Center(
      child: Material(
        color: Colors.transparent,
        child: Container(
          padding: const EdgeInsets.all(24.0),
          margin: const EdgeInsets.all(32.0),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(16.0),
            boxShadow: const [
              BoxShadow(
                color: Colors.black26,
                blurRadius: 10.0,
                offset: Offset(0, 4),
              ),
            ],
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text(
                'Animated Dialog Title',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 16),
              const Text(
                'This is a custom modal dialog with a beautiful fade and scale animation!',
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: const Text('Close'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void _showCustomAnimatedDialog(BuildContext context) {
    Navigator.of(context).push(
      _createFadeScaleDialogRoute(
        _buildAnimatedDialogContent(context),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Animated Modal Dialog'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => _showCustomAnimatedDialog(context),
          child: const Text('Show Animated Dialog'),
        ),
      ),
    );
  }
}

Customization and Further Enhancements

  • Curves: Experiment with different Curves (e.g., Curves.bounceOut, Curves.elasticOut, Curves.fastOutSlowIn) to achieve various animation styles and feels.
  • Durations: Adjust transitionDuration and reverseTransitionDuration to control the speed of the animation. Shorter durations feel snappier, longer durations feel more graceful.
  • Additional Transitions: You can combine more Transition widgets (e.g., SlideTransition, SizeTransition) within the transitionsBuilder for even more complex effects. For example, a slight slide in addition to fade and scale.
  • Gestures: Implement dismissible behavior by integrating GestureDetector to close the dialog when tapping outside, similar to standard dialogs.
  • Accessibility: Ensure your animations are not too fast or distracting, and consider providing options for users who prefer reduced motion in their device settings.

Conclusion

Implementing fade and scale animations for modal dialogs in Flutter significantly elevates the user experience. By leveraging PageRouteBuilder, FadeTransition, and ScaleTransition, developers can craft visually appealing and intuitive interactions that make applications feel more polished and professional. This approach offers fine-grained control over transition effects, allowing for highly customized and engaging UI patterns that delight users and improve overall application usability.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is