Skip to main content
Version: 2.0.0 Last Updated: 2026-04-19 Purpose: Detailed guide for implementing mobile navigation patterns in Encore OS
Navigation documentation index: NAVIGATION_GUIDE_INDEX.md
Major changes vs v1.4.0:
  • Removes references to MobileMenuSheet (consolidated into MobileNavMore per ResponsiveNav.tsx 2026-Q1).
  • Updates the component map to reflect the current MobileNav (4-slot dock + More drawer), MobileNavMore, MobileNavDockSheetContent, MobileSidebarNav (feature-flagged).
  • Adds the new BottomTabBar shared primitive for core-level bottom navs (CE today; HR/RH later).
  • Aligns CSS-var examples with production (--mobile-nav-height: 76px, --mobile-header-height: 48px, --mobile-header-context-height: 32px).
  • Codifies the mobile-component-location convention (cores//components/mobile/).
  • Edge-swipe back is now bound to the main scroll area (PR-3.2 in the mobile-gestures consistency plan), not just the header.

Overview

Encore OS uses a mobile-first navigation system:
  • Bottom tab bar for primary navigation (< 768 px).
  • Top header with logo, search, notifications, and a contextual primary action.
  • Mobile breadcrumbs (last 2 segments) for hierarchical context.
  • More drawer (MobileNavMore + MobileNavDockSheetContent) for secondary navigation, themes, sign-out, and module switching.
  • Desktop: Sidebar (SidebarProvider + AppSidebar + SidebarInset) at ≥ 768 px. Other nav modes (Dock, Taskbar) live in NavModeContext.

Quick Reference

Component map (current)

MobileMenuSheet was removed in 2026-Q1 — its responsibilities are now in MobileNavMore + MobileNavDockSheetContent.

Context-aware bottom navigation (2026-Q2)

MobileNav now operates in two modes:
  1. Platform mode (default): Shows the user’s 3 customizable shortcut slots + More. Active when no module is detected.
  2. Module mode: When the user is inside a module (detected via useModuleRouting), the bottom bar automatically transforms to show that module’s top navigation sections (derived from MODULE_REGISTRY.navGroups via useModuleBottomTabs), plus the More button.
The More drawer is also context-aware: when opened from inside a module, it auto-navigates to that module’s L2 groups instead of the top-level modules grid (initialModuleId prop). Dead core-level MobileBottomNav files in CL, PM, and HR were removed in 2026-Q2. CE’s MobileBottomNav / MobileCrmShell remain for CE-specific features (QuickLogFAB, offline indicator) but the CE tab bar will be superseded by the platform context-aware pattern.

Common patterns

Key requirements

  • Touch targets: ≥ 44 × 44 px (WCAG 2.1 AAA, Apple HIG). Use min-w-[44px] min-h-[44px].
  • Safe areas: all fixed surfaces apply env(safe-area-inset-*) via the CSS vars below.
  • Breakpoints: mobile < 768 px, desktop ≥ 768 px. Use useIsMobile() from @/shared/lib/hooks/use-mobile (do not use useMediaQuery for the mobile breakpoint).
  • Z-index: bottom nav uses z-50; app-lock overlay (PF-79) wins at z-[100].
  • Content padding: the <main> inside ResponsiveNav already pads top + bottom for header/nav heights using CSS vars.

Common issues


CSS Variables (Single Source of Truth)

Defined in src/index.css:
Helper utility classes (also in index.css):
  • .safe-area-top / .safe-area-bottom / .safe-area-left / .safe-area-right
  • .no-pull-refresh (overscroll-behavior-y: contain)
  • .scrollbar-thin / .scrollbar-x / .scrollbar-y for scroll-area styling

1. Architecture

The useEdgeSwipe for back navigation is bound to the mobile main scroll container (PR-3.2), so it can fire from anywhere in the left 20 px of the viewport — not just the 48 px header.

2. Bottom Navigation

Platform MobileNav

The platform-level bottom nav lives at src/platform/navigation/MobileNav.tsx and renders a compact single-row dock of up to 4 slots + a More button (no raised center FAB):
Shortcuts come from useMobileNavPreferences (per-user) filtered against permission via useMobileNavAllowedShortcuts. The list is configured in mobile-nav-config.ts. Long-pressing a slot (or “Edit Dock” in the More drawer) lets the user swap or reorder shortcuts. The raised center “morphing FAB” was removed. Per-screen quick actions (the contextual primary action + context actions) now live at the top of the More drawer. Users who want a quick-actions launcher on the bar can pin the Quick Actions shortcut (open-quick-actions) to a slot via the slot-swap sheet. Active-state visuals:
  • A sliding pill underline animates between the active item.
  • aria-current="page" is set on the active link.

Core-level BottomTabBar (new)

For modules that need a module-scoped bottom nav (today only CE; potentially HR / RH for field staff), use the shared primitive at src/platform/navigation/components/BottomTabBar.tsx:

Stacking with the platform nav

A core-level BottomTabBar does not replace the platform MobileNav — both can render. To avoid double-stacking:
  • Either offset the core nav above the platform nav (default), or
  • Hide the platform nav for the route by checking useNavigation().isSubModuleRoute and conditionally rendering. (Decision: keep both for now; CE has confirmed the dual-nav UX.)

3. More Drawer (MobileNavMore + MobileNavDockSheetContent)

Replaces the legacy MobileMenuSheet. Contents:
  1. User profile header with avatar.
  2. Notifications inbox link.
  3. AI Assistant launch.
  4. Theme toggle.
  5. Phone / Log Call (RingCentral integration).
  6. All modules grid (collapsible by category).
  7. Search.
  8. Sign out.
To customize the drawer, do not embed it manually — it’s instantiated inside MobileNav.

4. Safe Area Insets

Already wired into index.css. Consumers reference the CSS vars:
or with the helper class:

Test devices

  • iPhone X / XS / 11 / 12 / 13 / 14 / 15 (notched + home indicator)
  • iPhone 14 / 15 Pro (Dynamic Island)
  • Pixel 6 / 7 / 8 (home indicator)
  • iPad Pro / iPad Air

5. Touch Targets

Spacing: gap-2 (8 px) minimum between adjacent targets.

6. Mobile-Component Location Convention (new)

Cores have grown three different conventions for “where do mobile-only components live?”:
  • CE: dedicated src/cores/ce/components/mobile/ folder.
  • CL / HR: Mobile<Name>.tsx colocated with desktop peer.
  • FM / LO / RH: nothing (all responsive in one file).
Going forward, use one of these two patterns (in order of preference):
  1. Mobile-only components that don’t have a 1:1 desktop sibling → put under src/cores/{core}/components/mobile/.
  2. Mobile variants of existing components (small) → colocate as <Name>.mobile.tsx peer to <Name>.tsx.
Naming:
  • Mobile<Domain><Surface> (e.g. MobileVitalsKeypad, MobileBottomNav).
  • Use Sheet not Drawer / Modal in new file names (per root AGENTS.md). Existing names with Drawer are grandfathered until renamed (see MobileVitalsDrawer → MobileVitalsSheet rename in PR-2.5).
This convention is also pinned in src/cores/AGENTS.md and per-core AGENTS files where mobile work is active.

7. Edge Swipe Back Navigation (updated)

The hook (useEdgeSwipe) is now bound to the main mobile scroll container in ResponsiveNav rather than only MobileHeader. This means a left-edge swipe anywhere in the visible viewport — not just the 48 px header — triggers back navigation.
Visual indicator (1 px primary-color bar) overlays the viewport when the gesture is active. If a page does not want edge-swipe back (e.g. a horizontally-scrollable canvas), wrap the page root in a div with style={{ touchAction: 'pan-x' }} to opt out.

8. Offline Navigation

Use the existing useOnlineStatus hook. The MobileHeader shows an offline banner; MobileNav already hides routes that require online when offline.
PWA service worker caches the shell + critical assets per vite.config.ts Workbox config; navigation falls back to the cached shell when offline.

9. Navigation Error Boundaries

Use the platform RouteErrorBoundary (src/platform/navigation/components/RouteErrorBoundary.tsx) — wraps each route module. Reports to Sentry; exposes a “Retry” + “Go home” UI.

10. Testing Checklist

Functional

  • Bottom nav appears on mobile (< 768 px), hidden on desktop.
  • More drawer opens/closes.
  • Edge swipe back navigates one level (route depth ≥ 2).
  • Customize dialog (long-press More) saves preferences.
  • Core-level BottomTabBar filters by permission.

Safe areas

  • iPhone X+ / Pro models — no overlap with notch / Dynamic Island.
  • Pixel 7+ — bottom nav above home indicator.
  • Landscape orientation — left/right insets honored.

Touch targets

  • All bottom-nav buttons ≥ 44 × 44 px.
  • Header icon buttons ≥ 44 × 44 px.
  • Drawer rows ≥ 44 px tall.

Accessibility

  • aria-current="page" on active tab.
  • aria-label on every icon-only button.
  • Esc closes the More drawer.
  • Skip-to-content link works from keyboard.
  • Keyboard arrow keys navigate between bottom-nav slots (MobileNav already implements this).

Hydration / first paint

  • No desktop chrome flash on real mobile devices (PR-3.1 fix in useIsMobile).
  • Boot splash transitions cleanly.

Cross-browser

  • iOS Safari 14+ ✅
  • Android Chrome 90+ ✅
  • Firefox / Edge mobile

11. Mobile Breadcrumbs

MobileBreadcrumbs shows the last 2 segments of the route. Detail/edit pages set the dynamic trailing crumb via useEntityBreadcrumb(data, (e) => e.name) from @/shared/lib/hooks/useEntityBreadcrumb. Static labels come from BASE_ROUTE_LABELS in src/platform/navigation/route-labels.ts (audit: npm run audit:routes-navigation).
Pages MUST NOT import @/shared/ui/breadcrumb primitives directly — header auto-render is the standard.

12. Best Practices Summary

  1. Use CSS vars (--mobile-nav-height, --safe-area-inset-*) for all fixed-element spacing; never hard-code.
  2. Keep touch targets at 44 × 44 px minimum.
  3. Use useIsMobile for the 768-px breakpoint; useMediaQuery is reserved for non-standard breakpoints and is @deprecated for the mobile case.
  4. Place mobile-only components under src/cores/{core}/components/mobile/.
  5. Use the Sheet primitive for slide-up panels (not “Drawer” or “Modal” in new code).
  6. Use BottomTabBar for core-level mobile navs (CE today; HR/RH if needed).
  7. Use SwipeableCardShell (from @/platform/gestures) instead of hand-rolled *Swipeable.tsx wrappers.
  8. Set aria-current="page" on active tabs and aria-label on icon-only buttons.
  9. Honor prefers-reduced-motion automatically — Phase 1 gesture hooks already do this (PR-3.3).
  10. Test on real notched iOS + home-indicator Android devices (or Chrome DevTools device emulation).

13. Quick Actions Pattern (PF-20)

Quick actions are not in mobile navigation. They appear on overview pages via QuickActionsSection from @/platform/dashboard/components/QuickActionsSection. The mobile More drawer surfaces a “Quick Actions” entry that opens the command palette.

14. Mobile Swipe Gestures

See Mobile Gesture Guide v2.0.0 (companion document) for the complete catalog of swipe / pinch / multi-touch hooks and components, including SwipeableCardShell, MobilePullToRefresh, useEdgeSwipe, and Phase 2 user preferences.

15. References


Document status: Active Maintained by: Platform Team Last reviewed: 2026-04-19