Every app with more than one screen needs navigation, and Flutter models it as a stack: pushing a route puts a new screen on top, popping takes it off and reveals the one beneath. Get the stack mental model right and the rest — back buttons, system gestures, named routes — falls into place. This guide covers Navigator.push and MaterialPageRoute, going back with pop, named routes and the routes table, pushReplacement, and when to graduate to go_router, with paste-ready code.

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 nowEvery snippet below is paste-ready against current stable Flutter. Once you have the basics, passing data between screens and tabbed navigation are the natural next steps.
We'll push a screen, pop back, register named routes, swap a screen with pushReplacement, and weigh named routes against go_router.
If you'd rather watch a multi-screen app wired up from scratch, the channel builds real navigation flows step by step.
Push a new screen
The core call is Navigator.push, given a MaterialPageRoute whose builder returns the destination. The new screen slides in with the platform's standard transition and gets an automatic back button.
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailsScreen()),
);
},
child: const Text('Open details'),
)
Think of the route stack like a pile of cards: push drops a new card on top. The screen underneath stays alive in memory, ready to reappear when the top card is removed.
Go back with pop
To return to the previous screen, call Navigator.pop. You rarely need to do this manually for the app bar — the back button and the Android system back gesture call it for you — but it's essential for "Done" buttons and confirmations.
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Go back'),
)
Popping removes the top card and reveals the one beneath. Don't pop the very last route, though — there's nothing underneath, so it can close the app or throw. Use Navigator.canPop(context) to check first when you're unsure.
Named routes
Constructing a MaterialPageRoute at every call site gets repetitive. Named routes let you register screens once in a routes table on MaterialApp, then navigate by string.
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailsScreen(),
'/settings': (context) => const SettingsScreen(),
},
)
// Navigate from anywhere:
Navigator.pushNamed(context, '/details');
This centralises your navigation map in one readable place. For routes that need runtime values or guards, onGenerateRoute gives you a callback that builds the route from a RouteSettings object, which is also where you'd parse arguments.

Build multi-screen apps with confidence
The Complete Flutter Guide covers navigation, routing, and app architecture from first screen to shipped app.
Enrol nowpush vs pushReplacement
Sometimes you don't want the user to go back — after a successful login, returning to the sign-in form would be wrong. pushReplacement swaps the current route for the new one, removing the old screen from the stack entirely.
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomeScreen()),
);
Use push for normal drill-down navigation where back makes sense, and pushReplacement (or pushReplacementNamed) at one-way boundaries like sign-in, onboarding, and splash screens. To clear the whole stack — say, on logout — pushNamedAndRemoveUntil with a predicate of (route) => false resets to a single fresh route.
When to reach for go_router
The built-in Navigator is perfect for straightforward push-and-pop flows. Once you need deep links, web URLs that match your routes, or complex nested navigation, the imperative API gets unwieldy. That's the moment to adopt a declarative router — the go_router guide covers deep linking and nested navigation in full. Start with the basics here; reach for go_router when the app's routing genuinely grows beyond them.
Common mistakes
- Popping the last route. There's nothing beneath it; check
canPopor usepushReplacement. - Using the wrong context. A context above the Navigator can't find it; use one inside the current route.
- Forgetting pushReplacement after login. Plain
pushleaves the sign-in screen on the stack. - Hard-coding routes everywhere. Named routes (or go_router) centralise the map and reduce typos.
- Reaching for go_router too early. For simple push/pop flows the built-in Navigator is plenty.
Frequently asked questions
How do I navigate to a new screen in Flutter?
Call Navigator.push with a MaterialPageRoute that builds the destination; Navigator.pop returns to the previous screen.
What is the difference between push and pushReplacement in Flutter?
push stacks the new route on top so you can go back; pushReplacement swaps the current route, removing it from the stack. Use the latter after login.
What are named routes in Flutter?
String paths registered in the routes table on MaterialApp; navigate with pushNamed instead of building a route each time.
Why does Navigator.pop crash or do nothing in Flutter?
You popped the last route, or passed a context above the Navigator. Check canPop first and use a context inside the current route.
Further reads
Keep going with the tutorials that pair with this guide:
- Flutter Development Guide 2026 — the full Flutter hub.
- Flutter Pass Data Between Screens — send arguments and return results.
- Flutter BottomNavigationBar — multi-tab navigation that preserves screens.
- Flutter TabBar and TabBarView — swipeable tabs within a screen.
- go_router in Flutter — deep linking, nested navigation, and web URLs.
Sources: Flutter documentation — Navigator, MaterialPageRoute, RouteSettings, and the "Navigate to a new screen and back" and named-routes cookbook recipes (docs.flutter.dev). Verified against current stable Flutter.