An advanced navigation system for Flutter that enables typed routes, custom transitions, and robust overlay management (Banners, Modals, Sheets).
- Centralized Route Management: Define all your app routes in one place.
- Typed Route Arguments: Pass strongly-typed arguments to your routes safely, with validation.
- Custom Transitions: Easily implement custom page transitions (slide, fade, etc.) with a smart priority system.
- Versatile Presentations: Display any route as a full page, a modal popup, or a swipeable bottom sheet using
TraceRoute. - Semantic Navigation API: Create a reusable, semantic, and centralized navigation API for your app using
Traceobjects. - Deep Linking: Automatically parses URL query parameters and delivers them to new or existing screens.
- Advanced Overlays & Popups: Show sequential banners, complex modals, and multi-level popups.
...
browser provides powerful, unified methods for navigation.
If you navigate to a path that contains URL query parameters (e.g. /profile?id=123), browser automatically parses them into a DeepLinkParam object.
There are two ways to receive these parameters:
1. On a new screen
If the deep link pushes a new screen, that screen can get the parameters directly in its build method.
// In ProfileScreen's build method
final deepLinkParams = context.getArgument<DeepLinkParam>();
if (deepLinkParams != null) {
final userId = deepLinkParams.params['id']; // "123"
}2. On an existing screen (with Browser.watch)
If the deep link navigates to a screen that is already in the stack (like the HomeScreen), you can receive the parameters in the onAppear callback of Browser.watch.
// In HomeScreen's State
Widget build(BuildContext context) {
return Browser.watch(
onAppear: (context, deepLink) {
// The `deepLink` parameter will be populated here
if (deepLink != null) {
final message = deepLink.params['message'];
// ... update state with the message
}
},
child: ...
);
}A TraceRoute is an object that defines HOW a route is presented. The type of TraceRoute you provide determines the presentation style.
PageTraceRoute: The default. Presents the route as a standard, full-screen page.PopupTraceRoute: Presents the route as a modal dialog.SwipeTraceRoute: Presents the route as a swipeable bottom sheet.
This is the recommended way to pass data between screens.
1. Create an arguments class
class ProfileArgs extends RouteParams { ... }2. Add validation to your route
BrowserRoute(
path: '/profile',
page: ProfileScreen(),
validateArguments: (check, get) => check<ProfileArgs>(),
),3. Push with arguments
context.pushNamed(
'/profile',
args: [ProfileArgs(userId: '123')],
);4. Retrieve the arguments
final args = context.getArgument<ProfileArgs>();To receive a "result" from a screen that was popped, you must wrap the receiving widget with Browser.watch and check for your result arguments inside the onAppear callback.
// In the receiving screen (e.g., HomeScreen)
return Browser.watch(
onAppear: (context, deepLink) {
// Use getArgumentAndClean for one-time pop results
final popArgs = context.getArgumentAndClean<PopResultArgs>();
if (popArgs != null) {
// ... update state with popArgs.result
}
},
child: ...
);getArgument<T>(): Reads an argument without removing it. Use for data needed to build a screen (e.g., a product ID).getArgumentAndClean<T>(): Reads an argument and then removes it. Use for one-time events, like results from apop, to avoid processing the same event multiple times.
For large applications, you can use the Trace class to create a centralized, reusable, and semantic API for all your navigation events.
1. Centralize Paths
enum AppPath {
profile('/profile');
const AppPath(this.path);
final String path;
}2. Create a Trace Wrapper
class AppTrace extends Trace {
const AppTrace._({required super.path, super.args, super.traceRoute});
factory AppTrace.toProfile({ required String userId }) {
return AppTrace._(
path: AppPath.profile.path,
args: ProfileArgs(userId: userId),
);
}
}3. Use the Semantic API
AppTrace.toProfile(userId: '123').push(context);This package works directly with Flutter's default web routing strategy, which uses a "hash" (or fragment) in the URL.
By default, your Flutter web app's URLs will look like this:
https://yourapp.com/#/home
https://yourapp.com/#/profile/123?mode=edit
With this strategy, route arguments and parameters are managed on the client-side and work without any special configuration on your web server.
If you prefer cleaner, more SEO-friendly URLs without the #:
https://yourapp.com/home
https://yourapp.com/profile/123?mode=edit
You can enable the "path-based" routing strategy. To do so, follow these two steps:
-
Enable the Strategy in Flutter: Call
usePathUrlStrategy()at the beginning of yourmain()function inmain.dart.// main.dart import 'package:flutter/material.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; void main() { // Call this function before runApp() usePathUrlStrategy(); runApp(const MyApp()); }
-
Configure Your Web Server: This is a critical step. You must configure your production server (Nginx, Apache, Firebase Hosting, etc.) to redirect all requests to your
index.htmlfile. Without this, users who directly access an internal URL of your app will get a 404 error.For more details on how to configure your server, see the official Flutter documentation.
By following these steps, browser will work perfectly with the routing strategy you choose.
Big thanks go to these wonderful people who have contributed to the project:
![]() Eduardo Martínez Catalá |
![]() Cayetano Bañón Rubio |
![]() Jesus Bernabeu |


