Mount the scopes once.
Construct controllers at an application boundary and pass them into their matching hosts. The overlay host wraps ordinary content so temporary entries remain scoped to that area.
private readonly Navigator _navigator = new(new DashboardPage());
private readonly OverlayController _overlay = new();
new BackNavigation(
new OverlayHost(new NavigatorHost(_navigator), _overlay),
_navigator, _overlay);Use keys where route identity matters.
Push retains the previous route and its local state. Replace is the right choice when history is no longer useful.
_navigator.Push(new Route(
new WidgetKey($"project:{project.Id}"),
new ProjectDetails(project),
RouteTransition.Fade(TimeSpan.FromMilliseconds(180))));Let the top overlay own input.
Modals block the content below them. Keep required decisions non-dismissible and close through the returned handle or the dialog's action.
_overlay.ShowModal(
new Dialog(new Text("Delete this project?")),
new ModalOptions(
dismissOnBarrier: false,
dismissOnBack: false));Escape closes the topmost scope first.
BackNavigation resolves input in order: the top overlay, the active route, then nothing at the root. This prevents a required dialog from leaking Escape to the route underneath it.
Inactive routes remain mounted to preserve local state. Use Replace, PopToRoot or ClearAndPush for flows that do not need a full back stack.