Browse all of Kata

Navigation

Source docs/design/patterns/navigation.mdMarkdown

On this page

Where a screen sits, how you get back, and what is allowed to be a modal.

Part of the Konjo design language. The laws in the spine apply here unless this chapter contradicts them.

Konjo runs one root stack, five tab stacks, and thirty-four modal presentations, with headerShown: false everywhere — the app draws its own headers. That makes header behaviour a design decision rather than a platform default, and it has never been written down.

The shape

  • Four base tabs — Train | Events | Learn | Social — plus a conditional Teach for instructors, which sits fifth, between Train and Events. Profile is a route inside Social, not a tab.
  • Each tab owns a stack. A screen belongs to the tab whose job it serves, and it is pushed within that stack — never pushed onto a sibling tab.
  • Tabs keep their state. Leaving Learn mid-scroll and coming back returns you where you were. A tab that resets to the top on every visit is a tab people stop trusting.
  • Tapping the active tab scrolls to top, then on a second tap pops to the stack root. Both are conventions people already have.

Push or modal

Push when the thing is part of where you already are. Present a modal when it is a detour.

Push Modal
Relationship A deeper level of the same subject A separate task, then back
Back affordance ‹ Back, top left ✕ Close, top left
Examples A student from the roster, a class from a schedule Compose a post, log a session, sign a waiver
After it completes Stays on the stack Dismisses — to the list the record joined if that is not where it started, so a person who just created something is looking at it

Two rules that resolve most cases:

  • If it creates or edits something, it is a modal. The task has a beginning and an end and a person should be returned to what they were doing.
  • If it can be navigated past — deeper into a hierarchy — it is a push. Modals do not nest well and a person three modals deep has lost the thread.

A modal never contains a tab bar and never pushes to something the tabs also reach. If a modal wants to send someone to a tab, it dismisses first.

Headers

The app draws them, so they are consistent by choice rather than by framework.

  • Tab homes use ScreenHeader: display title left, up to four actions right (create, search, notifications, avatar). No back affordance — it is a root.
  • Pushed screens get a back affordance and no title in the bar. The screen's own display title sits in the content, where it can wrap and scale. A title duplicated in the bar and the body is the same destination twice in one viewport.
  • Modals get ✕ Close, top left, and the same in-content title.
  • Header glyphs are 24pt in 44pt targets, text.primary, and never more than four. The fifth action belongs in an overflow or in the content.
  • Never a title that collapses on scroll into a second copy of itself. Konjo has one title per screen, in the content.

Back

  • Back always goes back, to the previous screen, never "up" to a different one.
  • Android's hardware back and gesture back do the same thing as the header affordance. Currently only MediaLightbox handles BackHandler explicitly; anything that traps input — a lightbox, a sheet, an armed destructive action — must dismiss on hardware back rather than leaving the screen.
  • Unsaved work confirms before discarding. A form with typed content that gets dismissed asks once, plainly: Discard this report? — with the destructive option second. See forms.
  • Never disable back. If a person cannot leave, the screen is a bug, not a wall.

RN-web gets a real linking config; every route that is a thing should have a URL that resolves to it.

  • A deep link lands on the screen with a working back. Arriving at a student detail from a push notification and finding no way to the roster is a dead end.
  • A link to something gone shows the detail screen's error state, not a blank screen and not a redirect to the tab root. "This event was cancelled" answers the question; a silent bounce to Home does not.
  • Auth deep links are separate and stay with the auth handler.

Modals and sheets are different things

  • A modal is a full screen with its own task. Covered here.
  • A sheet is a partial overlay for a choice or a short form, dismissed by the scrim. See pickers and sheets.

Choosing a sheet for something with more than one step, or a modal for a single choice, is the most common structural mistake in this app.

Scroll

  • Content clears the tab bar: bottom inset plus 80. A last row half-hidden behind the bar is the most reported small bug in any tab app.
  • Scroll position is preserved on a tab, and reset on a pushed screen — a new record starts at the top.
  • Never nest a vertical scroll inside a vertical scroll.

Never

  • A screen pushed onto the wrong tab's stack.
  • A modal inside a modal inside a modal.
  • A title in the bar and the same title in the body.
  • A tab that forgets where you were.
  • A deep link that lands with no way back.
  • Disabling back to force a decision.