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:displaytitle 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
displaytitle 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
MediaLightboxhandlesBackHandlerexplicitly; 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.
Deep links and web URLs
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.