Flutter Custom Page Route Transitions

Coding Liquids blog cover featuring Sagnik Bhattacharya for Flutter Custom Page Route Transitions, with pageroutebuilder fundamentals, fade and slide transitionsbuilder composition, and transitionduration and reversetransitionduration.
Coding Liquids blog cover featuring Sagnik Bhattacharya for Flutter Custom Page Route Transitions, with pageroutebuilder fundamentals, fade and slide transitionsbuilder composition, and transitionduration and reversetransitionduration.

A route animation makes more sense on video, especially when forward and reverse durations differ.

Subscribe on YouTube@codingliquids

Customise only when navigation meaning benefits

A custom transition can distinguish a modal task, reveal spatial direction, or support a branded flow. Keep duration and direction consistent so users learn what back navigation will do.

Novel motion on every route slows navigation and conflicts with platform expectations.

Read the PageRouteBuilder callbacks

pageBuilder creates destination content and transitionsBuilder receives primary animation, secondary animation, and child. Build the page once through child and apply transition widgets around it.

Recreating expensive page content inside transitionsBuilder repeats work every animation frame.

Route<T> fadeSlideRoute<T>(Widget page) {
  return PageRouteBuilder<T>(
    pageBuilder: (_, animation, __) => page,
    transitionDuration: const Duration(milliseconds: 280),
    reverseTransitionDuration: const Duration(milliseconds: 220),
    transitionsBuilder: (_, animation, __, child) {
      final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic);
      return FadeTransition(
        opacity: curved,
        child: SlideTransition(
          position: Tween(begin: const Offset(0.06, 0), end: Offset.zero).animate(curved),
          child: child,
        ),
      );
    },
  );
}

An Instagram route strip compares entering, secondary, and reverse animation values frame by frame.

Follow me on Instagram@sagnikteaches

Slide with an Offset tween

Tween maps a fractional start such as Offset(1, 0) to Offset.zero for SlideTransition. Chain a CurveTween or CurvedAnimation to soften acceleration and set reverseTransitionDuration deliberately.

Offsets are relative to child size, so very large values move the page farther than one screen.

The Complete Flutter Guide course thumbnail

Build production-ready Custom Page Route Transitions features

The Complete Flutter Guide turns custom page route transitions into maintainable app architecture, polished UI, and testable production code.

Enrol now

Install go_router only for the declarative CustomTransitionPage example; PageRouteBuilder itself is part of Flutter.

flutter pub add go_router

import 'package:go_router/go_router.dart';

Combine fade and scale without rebuilding

Wrap ScaleTransition in FadeTransition and drive both from the same curved animation. Use a subtle scale such as 0.96 to 1 rather than making the route appear to zoom from nowhere.

Stacking several strong effects makes text harder to track and can trigger motion sensitivity.

class FadeSlideRoute<T> extends PageRouteBuilder<T> {
  FadeSlideRoute({required Widget page})
      : super(
          pageBuilder: (_, __, ___) => page,
          transitionDuration: const Duration(milliseconds: 280),
          reverseTransitionDuration: const Duration(milliseconds: 220),
          transitionsBuilder: (_, animation, __, child) {
            final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic);
            return FadeTransition(
              opacity: curved,
              child: SlideTransition(
                position: Tween(begin: const Offset(0.08, 0), end: Offset.zero).animate(curved),
                child: child,
              ),
            );
          },
        );
}

Package motion in a reusable route class

A FadeSlideRoute can extend PageRouteBuilder and accept the destination widget in its constructor. Keep its forward and reverse behaviour together so callers cannot accidentally diverge.

A global custom class should still respect full-screen dialogs and route result types.

GoRoute(
  path: '/details/:id',
  pageBuilder: (context, state) => CustomTransitionPage<void>(
    key: state.pageKey,
    child: Scaffold(body: Center(child: Text('Details ${state.pathParameters['id']}'))),
    transitionsBuilder: (_, animation, __, child) =>
        FadeTransition(opacity: animation, child: child),
  ),
)

Route motion policy is the subject of my LinkedIn post on deep links, back gestures, and restored stacks.

Connect on LinkedInSagnik Bhattacharya
The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Use CustomTransitionPage with go_router

go_router accepts a CustomTransitionPage from pageBuilder for declarative routes. Use state.pageKey as the page key and return the same fade or slide composition from transitionsBuilder.

Returning a raw Widget from builder cannot customise page-level transitions in the same way.

Coordinate motion with navigation state

A route transition has an entering animation and a secondary animation describing what happens while another route covers it. PageRouteBuilder exposes both, allowing the new page to move without leaving the previous page frozen in an accidental position. Keep the child supplied to transitionsBuilder intact and wrap it with the required transitions; rebuilding the page contents inside the animation callback adds avoidable work on every tick.

Forward and reverse motion should describe the same spatial relationship. A detail page sliding in from the end edge normally leaves in the opposite direction, and the edge must respect text direction. Pop gestures can drive the route through arbitrary intermediate values, so state changes cannot assume that every push reaches completion. Data saves, analytics, and destructive actions belong to navigation or domain events rather than animation status callbacks.

Choose duration and curve from the amount of visual change, not from a single application-wide constant. A small fade can finish quickly, while a full-screen container transform needs enough time to remain legible. Combine opacity, scale, and translation sparingly; multiple strong motions compete with the destination content. When animations are disabled, preserve route focus, back behaviour, and semantics while using an immediate or very short transition.

Verification should include push, system back, app-bar back, a cancelled predictive or interactive back gesture where supported, keyboard focus, screen-reader announcements, and rapid navigation attempts. Ensure the route is opaque only when it really covers what is beneath it, and set barrier semantics for modal variants. Profile the transition on a release-mode build with the destination loaded; a smooth blank scaffold says little about a page that decodes an image and lays out a long list during its first frames.

Deep links and restored navigation stacks may construct a route without the preceding visual context assumed by a bespoke transition. The page must therefore make sense from its first frame and not depend on a source widget being mounted. Centralise repeated transitions in the routing layer, but allow modal, replacement, and platform-adaptive routes to retain appropriate semantics. Tests should assert that arguments survive both imperative navigation and URL restoration. A transition that looks polished in one push call but breaks browser back, state restoration, or nested navigation is not yet a reusable route policy.

Navigation architecture should decide the transition before feature widgets begin pushing routes. Name a small set of policies—standard page, detail, modal, and no-motion restoration—and apply them through the router or a typed route factory. Each policy can choose platform-appropriate duration and curve while preserving arguments, result values, fullscreen-dialog behaviour, and state restoration. This avoids one screen quietly wrapping another in a transition that browser back or a nested navigator cannot reproduce.

Loading work should be scheduled with the first frames in mind. Resolve lightweight route arguments before the push, let the destination display a stable shell, and defer expensive decoding that would compete with the animation. Profile the real route in release mode with images and text present, then inspect both the UI and raster timelines. Screen readers should receive the destination title once, keyboard focus should move intentionally, and reduced-motion mode should keep exactly the same navigation semantics. A deep link that builds the page directly remains the decisive test of whether motion is decoration rather than hidden application state.

Common mistakes

  • Customise only when navigation meaning benefits: In custom route transitions, novel motion on every route slows navigation and conflicts with platform expectations; inspect this custom route transitions cause before changing another custom route transitions widget.
  • Read the PageRouteBuilder callbacks: In custom route transitions, recreating expensive page content inside transitionsBuilder repeats work every animation frame; inspect this custom route transitions cause before changing another custom route transitions widget.
  • Slide with an Offset tween: In custom route transitions, offsets are relative to child size, so very large values move the page farther than one screen; inspect this custom route transitions cause before changing another custom route transitions widget.
  • Combine fade and scale without rebuilding: In custom route transitions, stacking several strong effects makes text harder to track and can trigger motion sensitivity; inspect this custom route transitions cause before changing another custom route transitions widget.
  • Package motion in a reusable route class: In custom route transitions, a global custom class should still respect full-screen dialogs and route result types; inspect this custom route transitions cause before changing another custom route transitions widget.
  • Use CustomTransitionPage with go_router: In custom route transitions, returning a raw Widget from builder cannot customise page-level transitions in the same way; inspect this custom route transitions cause before changing another custom route transitions widget.

Frequently asked questions

How does customise only when navigation meaning benefits work in custom route transitions?

For custom route transitions, the starting rule is that a custom transition can distinguish a modal task, reveal spatial direction, or support a branded flow. Apply this custom route transitions rule first because customise only when navigation meaning benefits determines whether the custom route transitions pattern fits.

Why does slide with an Offset tween matter for custom route transitions?

In custom route transitions, chain a CurveTween or CurvedAnimation to soften acceleration and set reverseTransitionDuration deliberately. Keeping slide with an Offset tween at the custom route transitions call site exposes the custom route transitions return value directly.

What failure should I test first in custom route transitions?

First reproduce the custom route transitions case where a global custom class should still respect full-screen dialogs and route result types. The reusable route must preserve result typing and modal behaviour while applying motion only where that policy is appropriate.

How can I verify custom route transitions before release?

Exercise use CustomTransitionPage with go_router with real custom route transitions inputs on every shipped platform. Inspect the final custom route transitions callback or output; a successful custom route transitions button tap alone is not proof.

Further reads

Connect custom route transitions to the surrounding Flutter stack through these published tutorials:

Sources: Flutter framework and Dart API documentation; custom route transitions examples verified against current stable Flutter.