> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile Navigation Guide

> Version: 2.0.0 Last Updated: 2026-04-19 Purpose: Detailed guide for implementing mobile navigation patterns in Encore Health OS

**Version:** 2.0.0
**Last Updated:** 2026-04-19
**Purpose:** Detailed guide for implementing mobile navigation patterns in Encore Health OS

> **Navigation documentation index:** [`NAVIGATION_GUIDE_INDEX.md`](./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/{core}/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 Health 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)

| Component                        | Path                                                              | Purpose                                                    |
| -------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------- |
| `ResponsiveNav`                  | `src/platform/navigation/ResponsiveNav.tsx`                       | Top-level mobile/desktop switch                            |
| `MobileNav`                      | `src/platform/navigation/MobileNav.tsx`                           | 4-slot bottom dock with More button                        |
| `MobileNavMore`                  | `src/platform/navigation/MobileNavMore.tsx`                       | Sheet trigger for the More drawer                          |
| `MobileNavDockSheetContent`      | `src/platform/navigation/MobileNavDockSheetContent.tsx`           | Drawer body (modules, settings, theme, sign-out)           |
| `MobileHeader`                   | `src/platform/navigation/MobileHeader.tsx`                        | Top app bar (logo, back, search, notifications)            |
| `MobileBreadcrumbs`              | `src/platform/navigation/MobileBreadcrumbs.tsx`                   | Compact 2-segment crumb row                                |
| `MobileOrgSwitcher`              | `src/platform/navigation/MobileOrgSwitcher.tsx`                   | Multi-org switcher in context row                          |
| `MobileSidebarNav`               | `src/platform/navigation/MobileSidebarNav.tsx`                    | Feature-flagged (`pf.mobile_sidebar_nav`) full-tree drawer |
| `MobileModuleSwitcher`           | `src/platform/modules/MobileModuleSwitcher.tsx`                   | Module grid inside the More drawer                         |
| `MobileModuleIcon`               | `src/platform/modules/MobileModuleIcon.tsx`                       | Module icon helper                                         |
| `BottomTabBar`                   | `src/platform/navigation/components/BottomTabBar.tsx`             | Reusable mobile bottom-tab primitive (module-level navs)   |
| `useModuleBottomTabs`            | `src/platform/navigation/hooks/useModuleBottomTabs.ts`            | Derives bottom-bar tabs from MODULE\_REGISTRY navGroups    |
| `MobileModuleNavStrip` *(new)*   | `src/platform/navigation/components/MobileModuleNavStripImpl.tsx` | Scrollable chip bar for in-module section switching        |
| `useRoleBasedDefaults` *(new)*   | `src/platform/navigation/hooks/useRoleBasedDefaults.ts`           | Role-based default shortcut presets                        |
| `useNavigationFrequency` *(new)* | `src/platform/navigation/hooks/useNavigationFrequency.ts`         | Tracks module visit frequency for suggestions              |

> `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

```tsx theme={null}
// Platform mobile nav (auto-rendered by ResponsiveNav; do not embed manually)
<ResponsiveNav user={user}>
  <Routes />
</ResponsiveNav>

// Module bottom tabs are now auto-generated from MODULE_REGISTRY:
// - useModuleBottomTabs(currentModule) derives up to 3 tabs from navGroups
// - MobileNav renders them when isModuleRoute is true
// No manual BottomTabBar usage needed for new modules.

// CE legacy bottom nav (still used for QuickLogFAB/offline; will be migrated)
<BottomTabBar
  ariaLabel="CE mobile navigation"
  tabs={CE_MOBILE_TABS}
/>
```

### 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

| Symptom                          | Fix                                                                                                                                                      |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Content hidden behind bottom nav | The mobile `<main>` already adds `pb-[calc(var(--mobile-nav-height,64px) + var(--safe-area-inset-bottom,0px))]`. If you bypass it, add the same padding. |
| Safe area not honored            | Make sure the viewport meta tag has `viewport-fit=cover` (✅ in `index.html`).                                                                            |
| Touch target too small           | Add `min-w-[44px] min-h-[44px]` to the button.                                                                                                           |
| Hydration flash on mobile        | `useIsMobile` now reads `window.innerWidth` synchronously on mount (PR-3.1) — verify your code calls the hook from `@/shared/lib/hooks/use-mobile`.      |

***

## CSS Variables (Single Source of Truth)

Defined in [`src/index.css`](../../src/index.css):

```css theme={null}
:root {
  --safe-area-inset-top: env(safe-area-inset-top, 0px);
  --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
  --safe-area-inset-left: env(safe-area-inset-left, 0px);
  --safe-area-inset-right: env(safe-area-inset-right, 0px);
  --mobile-header-height: 48px;
  --mobile-header-context-height: 32px;
  --mobile-nav-height: 76px;
  --dock-height: 72px;
}
```

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

```text theme={null}
ResponsiveNav (handles mobile/desktop switching via useIsMobile)
├── Mobile (< 768px)
│   ├── MobileHeader (top app bar)
│   ├── <main> with pt/pb computed from CSS vars
│   ├── MobileNav (bottom dock — 4 slots + More)
│   │   └── MobileNavMore → MobileNavDockSheetContent
│   ├── ContextualActionFAB (above bottom nav)
│   └── (optional) MobileSidebarNav (feature flag pf.mobile_sidebar_nav)
└── Desktop (>= 768px)
    └── SidebarProvider + AppSidebar + SidebarInset
```

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 **4 slots + a More button**:

```text theme={null}
[shortcut 1] [shortcut 2] [shortcut 3] [shortcut 4] [More]
```

Shortcuts come from `useMobileNavPreferences` (per-user) filtered against permission via `useMobileNavAllowedShortcuts`. The list is configured in `mobile-nav-config.ts`. Long-pressing the More button opens the **Customize** dialog.

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`:

```tsx theme={null}
import { BottomTabBar } from '@/platform/navigation/components/BottomTabBar';

<BottomTabBar
  ariaLabel="CE mobile navigation"
  tabs={[
    { key: 'dashboard', label: 'Dashboard', icon: BarChart3, path: '/ce', permission: 'ce.dashboard.view' },
    { key: 'contacts',  label: 'Contacts',  icon: Users,     path: '/ce/contacts', permission: 'ce.contacts.view' },
    { key: 'partners',  label: 'Partners',  icon: Building2, path: '/ce/partners', permission: 'ce.partners.view' },
    { key: 'activities', label: 'Activities', icon: Phone,    path: '/ce/activities', permission: 'ce.activities.view' },
  ]}
/>
```

### 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:

```tsx theme={null}
<nav style={{ paddingBottom: 'var(--safe-area-inset-bottom, 0px)' }}>
```

or with the helper class:

```tsx theme={null}
<nav className="safe-area-bottom">
```

### 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

```tsx theme={null}
<Button className="min-h-[44px] min-w-[44px]" aria-label="Open menu">
  <Menu className="size-5" aria-hidden="true" />
</Button>
```

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.

```tsx theme={null}
// ResponsiveNav.tsx (mobile branch)
const edgeSwipe = useEdgeSwipe({
  onBack: () => navigate(-1),
  enabled: routeDepth >= 2,
  edgeWidth: 20,    // 20 px from left edge
  threshold: 100,   // 100 px swipe to commit
});
```

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.

```tsx theme={null}
import { useOnlineStatus } from '@/shared/hooks/useOnlineStatus';
const isOnline = useOnlineStatus();
```

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`).

```tsx theme={null}
useEntityBreadcrumb(patient, (p) => `${p.last_name}, ${p.first_name}`);
```

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](./mobile-gesture-guide.md)** 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

* Constitution §6 — PWA & UI requirements
* Spec: [PF-13 PWA & Mobile Navigation Improvements](../../specs/pf/specs/PF-13-pwa-mobile-navigation-improvements.md)
* Spec: [PF-37 Mobile Swipe Gestures](../../specs/pf/specs/PF-37-mobile-swipe-gestures.md)
* Spec: [PF-79 Mobile App Security (biometric / PIN / session lock)](../../specs/pf/specs/PF-79-mobile-app-security-biometric-pin-session-lock.md)
* Plan: [PF Mobile Touch & Scroll Improvement Plan](../../specs/pf/plans/PF-MOBILE-TOUCH-SCROLL-IMPROVEMENT-PLAN.md)
* Guide: [Mobile Gesture Guide v2.0.0](./mobile-gesture-guide.md)
* Guide: [Breadcrumb Implementation Guide](./breadcrumb-implementation-guide.md)
* Component: [`ResponsiveNav.tsx`](../../src/platform/navigation/ResponsiveNav.tsx)
* Component: [`MobileNav.tsx`](../../src/platform/navigation/MobileNav.tsx)
* Component: [`BottomTabBar.tsx`](../../src/platform/navigation/components/BottomTabBar.tsx) *(new)*
* Hook: [`useIsMobile`](../../src/shared/lib/hooks/use-mobile.tsx)
* WCAG 2.1 [Target Size](https://www.w3.org/WAI/WCAG21/Understanding/target-size.html)
* Apple HIG [Layout — Touch targets](https://developer.apple.com/design/human-interface-guidelines/layout)

***

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