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

# Breadcrumb Implementation Guide

> Version: 1.3.0 Last Updated: 2026-04-18 Purpose: Comprehensive guide for implementing breadcrumbs in Encore Health OS Platform

**Version:** 1.3.0
**Last Updated:** 2026-04-18
**Purpose:** Comprehensive guide for implementing breadcrumbs in Encore Health OS Platform

> **Navigation Documentation Index:** For an overview of all navigation documentation, see [NAVIGATION\_GUIDE\_INDEX.md](./NAVIGATION_GUIDE_INDEX.md)

***

## Overview

Breadcrumbs provide hierarchical navigation context, helping users understand their location within the application and navigate back to parent pages. Encore Health OS uses a unified breadcrumb system with:

* **Desktop:** Auto-generated breadcrumbs in the header (`DesktopHeader` + `Breadcrumbs`)
* **Mobile:** Same trail as desktop in the fixed **context row** under the primary `MobileHeader` row (`MobileBreadcrumbs`). Do not render a second duplicate trail in page bodies when the shell already shows crumbs.
* **Centralized policy:** `shouldShowBreadcrumbs` / `shouldShowMobileHeaderContextRow` in `src/platform/navigation/utils/breadcrumb-visibility.ts` (keep `ResponsiveNav` main `padding-top` aligned with whether the context row is visible).
* **Shared segment logic:** `src/platform/navigation/utils/generate-route-breadcrumb-segments.ts` (desktop and mobile use the same generator).
* **Centralized Labels:** All route labels in `route-labels.ts`

### Mobile placement contract

1. When `shouldShowBreadcrumbs(pathname)` is true, breadcrumbs appear only in `MobileHeader`’s second row (not inside scrollable `main`).
2. `PageContainer` `breadcrumbs` + `showBreadcrumbs` render **only** when the header policy is **false** for that route (avoids duplicate chrome). Prefer fixing policy/labels over in-page duplicates.
3. Two-segment module routes that still need crumbs (e.g. `/ce/referral-sources`, `/cl/pdmp`) are allowlisted in `breadcrumb-visibility.ts` (`MODULE_TWO_SEGMENT_BREADCRUMB_ROUTES`).
4. Never mount `<Breadcrumbs />` inside page content; use `useBreadcrumbLabel` / `useEntityBreadcrumb` for dynamic last segments.

***

## Quick Reference

### Basic Implementation

**Desktop (Auto-generated):**

```tsx theme={null}
// Automatically included in DesktopHeader
// No code needed - works automatically based on route
```

**Mobile:**

```tsx theme={null}
<MobileBreadcrumbs maxSegments={2} />
```

**Custom Route Labels:**

```typescript theme={null}
// In route-labels.ts — keys are full paths starting with `/`
export const ROUTE_LABELS: Record<string, string> = {
  '/hr/employees': 'Employees',
  '/hr/employees/:id': 'Employee Details',
  // ... more labels
};
```

### Common Patterns

**Adding Custom Label:**

1. Open `src/platform/navigation/route-labels.ts`
2. Add entry to `BASE_ROUTE_LABELS`: `'/route/path': 'Display Label'` (Title Case)
3. Breadcrumbs automatically use the label
4. `npm run audit:routes-navigation` enforces 100% coverage — every `<Route>` must have a label

**Mobile Truncation:**

* Default shows last 2 segments
* Adjust with `maxSegments` prop
* Horizontal scroll for overflow

**Component Location:**

* Desktop: `@/platform/navigation/Breadcrumbs.tsx`
* Mobile: `@/platform/navigation/MobileBreadcrumbs.tsx`
* Labels: `@/platform/navigation/route-labels.ts`

**For detailed architecture and implementation, see sections below.**

***

## 1. Architecture

### Component Structure

```
src/platform/navigation/
├── Breadcrumbs.tsx           # Desktop auto-generated breadcrumbs
├── MobileBreadcrumbs.tsx   # Mobile-optimized breadcrumbs (same segments as desktop)
├── route-labels.ts         # Centralized ROUTE_LABELS map (~150 routes)
├── MobileHeader.tsx        # Integrates MobileBreadcrumbs + org switcher row
└── utils/
    ├── breadcrumb-visibility.ts              # When header/context row shows crumbs
    └── generate-route-breadcrumb-segments.ts # Shared segment builder

src/shared/ui/
└── breadcrumb.tsx          # Base primitives (shadcn/ui)

src/shared/components/
└── PageContainer.tsx       # Optional breadcrumbs (suppressed when header shows auto crumbs)
```

### Data Flow

1. User navigates to a route (e.g., `/hr/employees/123/edit`)
2. `Breadcrumbs.tsx` parses the pathname into segments
3. Each segment is looked up in `ROUTE_LABELS`
4. Breadcrumb trail is rendered with links to parent routes

### Breadcrumb Flow Diagram

```mermaid theme={null}
flowchart LR
    User[User Navigates] --> Route[Route Change]
    Route --> Parse[Parse Pathname]
    Parse --> Segments[Route Segments]
    Segments --> Lookup[Lookup in ROUTE_LABELS]
    Lookup -->|Found| Label[Display Label]
    Lookup -->|Not Found| Format[Format Segment]
    Label --> Render[Render Breadcrumbs]
    Format --> Render
    Render --> Desktop[Desktop: Full Trail]
    Render --> Mobile[Mobile: Truncated Trail]
```

## 2. Component Reference

### 2.1 Base Primitives (`@/shared/ui/breadcrumb`)

| Component             | Purpose                              |
| --------------------- | ------------------------------------ |
| `Breadcrumb`          | Container wrapper with nav semantics |
| `BreadcrumbList`      | Ordered list of breadcrumb items     |
| `BreadcrumbItem`      | Individual breadcrumb segment        |
| `BreadcrumbLink`      | Clickable link to parent page        |
| `BreadcrumbPage`      | Current page (non-clickable)         |
| `BreadcrumbSeparator` | Visual separator (ChevronRight)      |
| `BreadcrumbEllipsis`  | Collapsed segments indicator         |

### 2.2 Auto-Generated (`@/platform/navigation/Breadcrumbs.tsx`)

Automatically generates breadcrumbs based on the current route path. Used in `DesktopHeader`.

**Features:**

* Parses pathname into segments
* Looks up labels in `ROUTE_LABELS`
* Handles module context
* Falls back to formatted path segments

### 2.3 Mobile (`@/platform/navigation/MobileBreadcrumbs.tsx`)

Mobile-optimized breadcrumbs with truncation and scroll.

**Props:**

| Prop          | Type   | Default | Description              |
| ------------- | ------ | ------- | ------------------------ |
| `maxSegments` | number | 2       | Maximum visible segments |

**Features:**

* Shows last N segments
* Horizontal scroll for overflow
* 44px touch targets
* Safe area support

### 2.4 Route Labels (`@/platform/navigation/route-labels.ts`)

Centralized map of route paths to display labels. Keys are full URL paths
starting with `/`. The map currently contains \~1,140 entries covering every
declared `<Route>` in `src/routes/*.tsx` — `npm run audit:routes-navigation`
enforces this coverage and exits non-zero if any route is missing a label.

```typescript theme={null}
export const BASE_ROUTE_LABELS: Record<string, string> = {
  // Platform
  '/settings': 'Settings',
  '/profile': 'My Profile',

  // HR Module
  '/hr': 'Workforce',
  '/hr/employees': 'Employees',
  '/hr/employees/:id': 'Employee Details',

  // ... ~1,140 total routes
};

// Re-exported as ROUTE_LABELS for downstream consumers.
export const ROUTE_LABELS: Record<string, string> = { ...BASE_ROUTE_LABELS };
```

### 2.5 Hub Tab Labels (`@/platform/navigation/hub-tab-labels.ts`)

For tabbed-hub pages that pass the active tab via `?tab=`, the breadcrumb
appends the tab label as the trailing segment (e.g.
`HR > Recruiting > Candidates`). The map supports both literal hub paths
(`/hr/ats`) and parameterized paths (`/cl/charts/:chartId`,
`/pm/patients/:patientId`) via single-segment wildcard matching.

```typescript theme={null}
export const HUB_TAB_LABELS: Record<string, Record<string, string>> = {
  '/hr/ats': { dashboard: 'Dashboard', candidates: 'Candidates', /* ... */ },
  '/cl/charts/:chartId': { summary: 'Summary', notes: 'Progress Notes', /* ... */ },
};
```

### 2.6 Dynamic entity labels (`@/shared/lib/hooks/useEntityBreadcrumb`)

The **preferred** way to set the trailing crumb to an entity name (e.g.
employee full name, invoice number, claim ID) on detail pages.

```typescript theme={null}
import { useEntityBreadcrumb } from '@/shared/lib/hooks/useEntityBreadcrumb';

const { data: employee } = useEmployeeDetail(id);
useEntityBreadcrumb(employee, (e) => e.profile?.full_name ?? 'Employee');
```

Under the hood this calls `useBreadcrumbLabel(label)`, which sets the label
for the current `location.pathname` in `BreadcrumbContext` and clears it on
unmount. Both desktop and mobile breadcrumbs then render the entity name in
place of the static `ROUTE_LABELS` value for that path.

> **Coverage check:** `npm run audit:breadcrumb-coverage` lists detail pages
> that don't yet set a dynamic label so they can be migrated. Add new
> detail pages to this pattern by default.

***

## 3. Implementation Patterns

### Pattern 1: Auto-Generated (Recommended)

**When to use:** Most pages where route structure matches breadcrumb trail.

```typescript theme={null}
// No explicit breadcrumbs needed - handled by DesktopHeader
export default function EmployeesPage() {
  return (
    <PageContainer>
      <PageHeader title="Employees" />
      <EmployeesList />
    </PageContainer>
  );
}
```

**Result:** HR > Employees

### Pattern 2: Dynamic entity name via `useEntityBreadcrumb` (preferred for detail pages)

**When to use:** Detail/edit pages with a `:id` segment where the trailing
crumb should be the entity name (employee, invoice, patient, etc.).

```typescript theme={null}
import { PageContainer } from '@/shared/components/PageContainer';
import { useEntityBreadcrumb } from '@/shared/lib/hooks/useEntityBreadcrumb';

export default function DocumentDetailPage() {
  const { data: document } = useDocument(id);
  useEntityBreadcrumb(document, (d) => d.title);

  return (
    <PageContainer>
      <h1>{document?.title}</h1>
      <DocumentViewer document={document} />
    </PageContainer>
  );
}
```

**Result:** Documents > Meeting Notes 2025-01-15

**Recommendation:** the page H1 should include the same identifier as the
breadcrumb tail. On mobile the breadcrumb truncates to the last 2 segments,
so a generic H1 ("Document Details") leaves the user with no entity context
besides the truncated crumb.

### Pattern 3: Explicit `PageContainer breadcrumbs` (fallback)

**When to use:** Pages that need a custom trail not derivable from the route
(e.g. wizards, multi-step flows, two-segment pages outside the auto-show
policy). For most routes the header already auto-renders the trail and
`PageContainer` suppresses the inline copy — so prefer Pattern 1 or 2.

```typescript theme={null}
<PageContainer
  breadcrumbs={[
    { label: 'Documents', href: '/documents' },
    { label: document?.title ?? 'Loading...' },
  ]}
  showBreadcrumbs
>
  ...
</PageContainer>
```

### ⛔ Anti-pattern: importing breadcrumb primitives directly in pages

Do **not** import `@/shared/ui/breadcrumb` primitives in core or feature
pages. The shadcn primitives are reserved for `PageContainer`,
`Breadcrumbs.tsx`, and `MobileBreadcrumbs.tsx`. Pages should only ever rely
on:

1. The auto-generated header trail (Pattern 1)
2. `useEntityBreadcrumb` / `useBreadcrumbLabel` for dynamic entity names (Pattern 2)
3. `PageContainer breadcrumbs={[…]} showBreadcrumbs` for custom trails (Pattern 3)

***

## 4. Adding New Routes

When creating new features, add route labels to `BASE_ROUTE_LABELS` in
`route-labels.ts`. Keys are **full URL paths** starting with `/`; values are
**Title Case**.

```typescript theme={null}
// src/platform/navigation/route-labels.ts

const BASE_ROUTE_LABELS: Record<string, string> = {
  // ... existing labels

  // Add your new routes
  '/my-feature': 'My Feature',
  '/my-feature/:id': 'Feature Details',
  '/my-feature/sub-page': 'Sub Page',
};
```

### Naming Conventions

| ✅ Do                                  | ❌ Don't                                           |
| ------------------------------------- | ------------------------------------------------- |
| `'/hr/employees': 'Employees'`        | `'/hr/employees': 'Employee Management'`          |
| `'/fa/work-orders': 'Work Orders'`    | `'/fa/work-orders': 'work orders'`                |
| `'/hr/employees/new': 'New Employee'` | `'/hr/employees/new': 'Create New Employee Form'` |

### Fallback Behavior

If a route is not in `ROUTE_LABELS`, the system:

1. Takes the path segment (e.g., `work-orders`)
2. Replaces hyphens with spaces
3. Capitalizes the first letter of each word
4. Result: "Work Orders"

> Always add an explicit label rather than relying on the fallback —
> `audit:routes-navigation` enforces 100% coverage.

### Two-segment leaf pages (e.g. `/ce/referral-sources`)

By default `shouldShowBreadcrumbs` hides crumbs on two-segment module
routes (`/{core}/{page}`), since the `ModuleSwitcher` already conveys
module context. If a particular two-segment leaf page still benefits from
the trail (e.g. `/ce/referral-sources`, `/cl/pdmp`), add it to
`MODULE_TWO_SEGMENT_BREADCRUMB_ROUTES` in
`src/platform/navigation/utils/breadcrumb-visibility.ts`.

***

## 5. Mobile Considerations

### MobileBreadcrumbs Integration

Mobile breadcrumbs are automatically included in `MobileHeader`:

```typescript theme={null}
// src/platform/navigation/MobileHeader.tsx
<header>
  {/* Logo, menu, notifications */}
</header>
<div className="border-b bg-background">
  <MobileBreadcrumbs maxSegments={2} />
</div>
```

### Truncation Behavior

| Segments | Display                |
| -------- | ---------------------- |
| 1        | Home                   |
| 2        | Parent > Current       |
| 3+       | ... > Parent > Current |

### Touch Targets

All breadcrumb links MUST have:

* Minimum 44x44px touch area
* Adequate spacing (8px minimum between items)
* Visual feedback on touch

***

## 6. Accessibility

### Required Attributes

* `<nav aria-label="Breadcrumb">` on container
* `<ol>` for breadcrumb list (semantic ordering)
* `aria-current="page"` on current page item

### Keyboard Navigation

* All links must be focusable
* Tab order follows visual order
* Focus indicators must be visible

### Screen Readers

The breadcrumb structure announces:

* "Breadcrumb, navigation"
* Each link in order
* "Current page: \[Page Name]" for last item

***

## 7. Testing Checklist

### Unit Tests

* [ ] Route parsing generates correct segments
* [ ] `ROUTE_LABELS` lookup returns expected values
* [ ] Fallback formatting works correctly
* [ ] Module context is handled

### Integration Tests

* [ ] Breadcrumbs update on navigation
* [ ] Links navigate to correct routes
* [ ] Mobile truncation works at breakpoints

### Accessibility Tests

* [ ] Screen reader announces structure correctly
* [ ] Keyboard navigation works
* [ ] Focus indicators visible

### Visual Tests

* [ ] Desktop breadcrumbs render correctly
* [ ] Mobile breadcrumbs truncate properly
* [ ] Touch targets meet 44px requirement
* [ ] Safe areas respected on mobile

***

## 8. Common Mistakes & Anti-Patterns

### ❌ Importing breadcrumb primitives in pages

```typescript theme={null}
// ❌ WRONG: pages should never import @/shared/ui/breadcrumb
import { Breadcrumb, BreadcrumbList } from '@/shared/ui/breadcrumb';
```

```typescript theme={null}
// ✅ CORRECT: rely on the auto-generated header trail; for dynamic labels
//             use useEntityBreadcrumb / useBreadcrumbLabel
import { useEntityBreadcrumb } from '@/shared/lib/hooks/useEntityBreadcrumb';
useEntityBreadcrumb(employee, (e) => e.profile?.full_name);
```

### ❌ Detail page without a dynamic label

```tsx theme={null}
// ❌ WRONG: trailing crumb is the static "Employee Details" instead of the
//          employee's name. On mobile (which truncates to last 2 segments)
//          users lose entity identity entirely.
export default function EmployeeDetailPage() {
  const { data: employee } = useEmployeeDetail(id);
  return <PageContainer><EmployeeProfile employee={employee} /></PageContainer>;
}
```

```tsx theme={null}
// ✅ CORRECT: trailing crumb is the employee name
export default function EmployeeDetailPage() {
  const { data: employee } = useEmployeeDetail(id);
  useEntityBreadcrumb(employee, (e) => e.profile?.full_name ?? 'Employee');
  return <PageContainer><EmployeeProfile employee={employee} /></PageContainer>;
}
```

> Run `npm run audit:breadcrumb-coverage` to see which detail pages still
> need this migration.

### ❌ Duplicate "Back to X" link alongside breadcrumbs

```tsx theme={null}
// ❌ WRONG: page already shows breadcrumbs and a top-left back-icon button
<PageContainer breadcrumbs={[...]} showBreadcrumbs>
  <Link to="/cl/in-basket"><ArrowLeft /> Back to In-Basket</Link>
  ...
</PageContainer>
```

```tsx theme={null}
// ✅ CORRECT: use DetailPageLayout backHref for the icon button; let the
//             breadcrumb provide the parent link
<DetailPageLayout backHref="/cl/in-basket" title={item.title}>
  ...
</DetailPageLayout>
```

### ❌ Forgetting to add route labels

```typescript theme={null}
// ❌ WRONG: missing from route-labels.ts → audit:routes-navigation fails
```

```typescript theme={null}
// ✅ CORRECT: add a Title Case label to BASE_ROUTE_LABELS
'/my-feature/:id': 'Feature Details',
```

### ❌ Inconsistent label format

```typescript theme={null}
// ❌ WRONG
export const BASE_ROUTE_LABELS = {
  '/hr/employees': 'employees',         // lowercase
  '/hr/employees/:id': 'Employee',      // ambiguous (singular)
};
```

```typescript theme={null}
// ✅ CORRECT
export const BASE_ROUTE_LABELS = {
  '/hr/employees': 'Employees',
  '/hr/employees/:id': 'Employee Details',
};
```

### ❌ Breadcrumbs on module dashboards

```tsx theme={null}
// ❌ WRONG: breadcrumbs render on /hr/dashboard ("HR > Dashboard") — noise
```

```tsx theme={null}
// ✅ CORRECT: shouldShowBreadcrumbs hides them automatically on top-level
//             module pages; do not override the policy on dashboards.
```

### ❌ Page H1 doesn't match breadcrumb tail

```tsx theme={null}
// ❌ WRONG: H1 is generic, breadcrumb tail is the entity identifier
<h1>Statement Run</h1>
// breadcrumb: PM > Statement Runs > Mar 18, 2026 10:42 AM
// Mobile users (after truncation) only see the date — no context
```

```tsx theme={null}
// ✅ CORRECT: H1 includes the same identifier as the breadcrumb tail
<h1>Statement Run — {formatDate(run.run_started_at)}</h1>
```

***

## 9. Quick Reference

### When to Show Breadcrumbs

| Page Type           | Show Breadcrumbs? |
| ------------------- | ----------------- |
| Module dashboard    | ❌ No              |
| Top-level list      | ❌ No              |
| Detail/edit page    | ✅ Yes             |
| Nested settings     | ✅ Yes             |
| Multi-step workflow | ✅ Yes             |

### File Locations

| Purpose                                          | File                                                     |
| ------------------------------------------------ | -------------------------------------------------------- |
| Base components (do not import in pages)         | `src/shared/ui/breadcrumb.tsx`                           |
| Desktop auto-gen                                 | `src/platform/navigation/Breadcrumbs.tsx`                |
| Mobile component                                 | `src/platform/navigation/MobileBreadcrumbs.tsx`          |
| Route labels (single source of truth)            | `src/platform/navigation/route-labels.ts`                |
| Hub-tab labels (`?tab=`)                         | `src/platform/navigation/hub-tab-labels.ts`              |
| Visibility policy / 2-segment allowlist          | `src/platform/navigation/utils/breadcrumb-visibility.ts` |
| PageContainer (with optional `breadcrumbs` prop) | `src/shared/components/PageContainer.tsx`                |
| DetailPageLayout (with `backHref`)               | `src/shared/components/DetailPageLayout.tsx`             |
| Dynamic-label hook                               | `src/shared/lib/hooks/useEntityBreadcrumb.ts`            |
| Lower-level dynamic-label hook                   | `src/platform/navigation/useBreadcrumbLabel.ts`          |
| Breadcrumb context                               | `src/platform/navigation/BreadcrumbContext.tsx`          |

### Audits

| Command                             | Checks                                                                                                               |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `npm run audit:routes-navigation`   | Every `<Route>` has a label; every nav entry resolves to a route; nav entries have labels                            |
| `npm run audit:breadcrumb-coverage` | Detail/edit pages set a dynamic label via `useEntityBreadcrumb` / `useBreadcrumbLabel` / explicit `breadcrumbs` prop |

***

## Related Documentation

### Standards

* [Constitution §6.3.1](../../constitution.md) - Breadcrumb standards and requirements

### Navigation Guides

* [Mobile Navigation Guide](./mobile-navigation-guide.md) - Section 12: Mobile Breadcrumb Patterns
* [Navigation Guide Index](./NAVIGATION_GUIDE_INDEX.md) - Complete navigation documentation index
* [Navigation Standard](../architecture/standards/NAVIGATION_STANDARD.md) - Navigation patterns and policies

### External References

* [shadcn/ui Breadcrumb](https://ui.shadcn.com/docs/components/breadcrumb) - Component reference
