Navigation

Three different things, deliberately kept apart: a bar at the top of a screen, tabs that switch the whole app, and tabs that switch content within one screen.

App bar

An App Bar holds up to three things — typically a back icon, a title and one action. With three, the middle one takes the spare width so the title centres.

Placed as the first thing on a screen, it spans the full width and sits flush at the top, outside the screen's padding. Anywhere else it behaves like an ordinary row, which is occasionally what you want.

Bottom navigation

The Tabs panel on the left adds a tab bar for the whole app. Each tab names a screen as its root, and the bar stays mounted while the screen beneath it changes.

One per appA design has at most one bottom navigation.
2 to 5 tabsEach with a label, an icon and a root screen.
SwitchingReplaces the visible screen rather than stacking it — no back arrow appears.

A button anywhere can switch tabs too, using the selectTab action — a “go to your profile” button on a home screen, for instance.

Switching returns to the tab's root

Leaving a tab and coming back opens its root screen rather than wherever you were within it. Per-tab history is not kept yet; when it is, designs built now will not need changing.

Tabs within a screen

The Tabs widget switches content inside one screen — All, Active, Archived over the same list. Each child is a panel, and its Tab label names the tab above it. Two to five panels.

The canvas shows the first panel, because designing a tab means selecting its panel in the layer tree. Switching happens in preview.

Moving between screens

Any tappable element can carry a navigate action, which opens another screen on top of the current one, and back returns from it. That is a stack: it remembers where you came from.

A decision between two destinations is a branch: one boolean — an app value like app.seenOnboarding, a toggle, a mutation flag — and two arms. Whether someone is signed in is not a branch; that is the auth gate's job.

In the exported app every screen is a typed route: ProductRoute(productId: id).push(context), with the path and its parameters checked by the compiler.

selectTab is not a stack — it replaces. Choosing the wrong one is the usual cause of a back arrow appearing where none belongs.

In the exported app

Screens become named routes. Bottom navigation becomes a shell widget holding an IndexedStack and a NavigationBar, and the app opens on it rather than on a route. In-screen tabs become a DefaultTabController with a TabBar and TabBarView.