# ADR-081: Safe-Area Inset Rendering for Notched and Rounded-Corner Devices

## Status

Accepted, 2026-06-27.

## Status History

```yaml
status_history:
  - date: 2026-06-27
    status: Proposed
    changed_by: hkl
    reason: Documenting the complete safe-area contract for notched Android devices and rounded-corner iOS devices
    changed_via: adr-kit (360lm)
  - date: 2026-06-27
    status: Accepted
    changed_by: hkl
    reason: Field teams use phones with notches, punch-hole cameras, and rounded corners; safe-area CSS is mandatory for content not to be hidden behind device hardware
    changed_via: adr-kit (360lm)
```

## Context

360lm PWAs run on Android and iOS field devices in standalone mode (home-screen app, no browser chrome). Many of these devices have:
- Notches (rectangular or teardrop-shaped camera cutouts at the top)
- Punch-hole cameras (circular cutouts)
- Rounded corners that reduce usable screen area
- Home indicators / gesture bars at the bottom (iOS and Android)
- Left/right Safe Area insets on phones in landscape mode

Without Safe Area padding, interactive elements render **behind** these physical features:
- Bottom navigation buttons hide behind the home indicator
- Sticky footers and floating action buttons become unreachable
- Top modals and overlays are clipped by status bars and notches
- Landscape layouts use the full width, extending into rounded corner zones

**Current state:**
- ADR-076 mandates `viewport-fit=cover` in the viewport meta tag — this activates CSS `env(safe-area-inset-*)` variables
- ADR-002 mandates `shared/safe-bottom.css` which applies `max(0px, env(safe-area-inset-bottom))` to sticky bottom elements
- ADR-003 requires long lists to scroll the root document with `position: sticky` chrome
- ADR-072 (canvas editor) fullscreen overlays must account for all four Safe Area insets

This ADR documents the **complete safe-area contract**: which CSS variables are available, where they must be applied, which elements must respect safe areas, and patterns for using them correctly.

## Decision

**Safe-Area inset handling is mandatory for all 360lm PWAs.** Use CSS `env(safe-area-inset-*)` values in the following pattern:

### 1. Bottom Safe Area (home indicator / gesture bar)

**Applies to:** sticky bottom bars, floating action buttons, bottom sheets, fixed footers, sticky chrome

**Pattern:**
```css
.sticky-bottom {
  padding-bottom: max(<existing-value>px, env(safe-area-inset-bottom));
}
```

**Examples:**
```css
.action-bar {
  padding-bottom: max(16px, env(safe-area-inset-bottom));
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
}

.sticky-form-footer {
  padding-bottom: max(12px, env(safe-area-inset-bottom));
  position: sticky;
  bottom: 0;
}
```

**Key rule:** Always use `max()` — never just `env(safe-area-inset-bottom)` alone. Devices without notches return `0px`, which would remove your padding on those devices.

**Shared stylesheet:** All PWAs must include `<link rel="stylesheet" href="/shared/safe-bottom.css">` (ADR-002). This stylesheet provides a base `.safe-bottom` utility class and ensures consistency.

### 2. Top Safe Area (status bar + notch)

**Applies to:** fullscreen overlays, modals that extend to the top, canvas editors (ADR-072), lightboxes

**Pattern:**
```css
.fullscreen-overlay {
  padding-top: max(<existing-value>px, env(safe-area-inset-top));
}
```

**Examples:**
```css
.modal-fullscreen {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding-top: max(0px, env(safe-area-inset-top));
  padding-bottom: max(0px, env(safe-area-inset-bottom));
}

.canvas-editor {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding-top: max(8px, env(safe-area-inset-top));
  padding-bottom: max(8px, env(safe-area-inset-bottom));
  padding-left: max(0px, env(safe-area-inset-left));
  padding-right: max(0px, env(safe-area-inset-right));
}
```

**Note:** Canvas editor (ADR-072) must account for all four insets so annotation tools and controls don't extend behind rounded corners or notches.

### 3. Left/Right Safe Area (rounded corners in landscape)

**Applies to:** fullscreen overlays in landscape mode, maps (ADR-004), horizontal scrollers that touch the viewport edges

**Pattern:**
```css
.landscape-aware-element {
  padding-left: max(<existing-value>px, env(safe-area-inset-left));
  padding-right: max(<existing-value>px, env(safe-area-inset-right));
}
```

**Examples:**
```css
.fullscreen-map {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding-left: max(0px, env(safe-area-inset-left));
  padding-right: max(0px, env(safe-area-inset-right));
  padding-top: max(0px, env(safe-area-inset-top));
  padding-bottom: max(0px, env(safe-area-inset-bottom));
}

.horizontal-scroller {
  overflow-x: auto;
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}
```

**Note:** Most phones have zero left/right insets; this becomes relevant in landscape mode or on phones with side notches (rare, but supported for future compatibility).

### 4. Implementation Checklist

When implementing a new PWA or modifying layouts:

- [ ] Viewport meta tag includes `viewport-fit=cover` (ADR-076)
- [ ] PWA links `<link rel="stylesheet" href="/shared/safe-bottom.css">` (ADR-002)
- [ ] Sticky bottom elements use `max(Xpx, env(safe-area-inset-bottom))`
- [ ] Fullscreen overlays apply padding for top, bottom, left, right insets as needed
- [ ] Canvas editor applies all four insets (ADR-072)
- [ ] No hardcoded padding values that ignore safe areas on elements extending to edges
- [ ] Test on notched device (Android with notch, or iOS simulator)

**Decision Maker:** hkl

## Alternatives Considered

1. **Rely on `viewport-fit=contain` (the default).**
   - Rejected: Wastes screen real estate; field users see black letterbox bars on notched devices. `viewport-fit=cover` is better.

2. **Apply Safe Area padding inline in each PWA without a shared stylesheet.**
   - Rejected: Creates code duplication and drift risk. A shared stylesheet (ADR-002) ensures consistency.

3. **Use only bottom Safe Area, ignore top/left/right.**
   - Rejected: While bottom is most critical (home indicator is present on all phones), fullscreen overlays and landscape layouts need all four insets. Incomplete approach leaves edge cases exposed.

4. **Apply raw `env(safe-area-inset-bottom)` without `max()`.**
   - Rejected: Devices without notches return `0px`, removing your intended padding. Always use `max()` to preserve intent on all devices.

5. **Use JavaScript to dynamically apply Safe Area values.**
   - Rejected: CSS `env(safe-area-inset-*)` is the platform-native solution; JS adds complexity and potential flash-of-unstyled-content. Pure CSS is simpler and more reliable.

6. **Let each PWA choose whether to use Safe Area handling.**
   - Rejected: Field users across all PWAs would experience inconsistent UX (some apps have content hidden behind notches, others don't). Mandatory implementation ensures uniformity.

## Consequences

### Positive

- **Content never hidden behind device hardware.** Home indicator, notches, rounded corners, gesture bars are all accounted for.
- **Consistent UX across all PWAs.** Every 360lm app respects Safe Areas; field users encounter no surprises.
- **Accessible to users in landscape mode.** Landscape layouts work correctly even on phones with side notches or rounded corner insets.
- **Simple implementation pattern.** The `max(Xpx, env(safe-area-inset-*))` pattern is easy to remember and apply.
- **Zero performance cost.** CSS variables are resolved at render time; no JavaScript overhead.
- **Future-proof.** Safe Area insets are a W3C standard; support will only expand.

### Trade-offs

- **Developers must know the pattern.** Not automatic; requires developer awareness and code review.
- **Testing requires notched devices or simulators.** Desktop Chrome DevTools can simulate Safe Area values, but real device testing is recommended for fullscreen overlays.
- **Padding values must be revisited for each element.** Not a one-size-fits-all solution; each sticky/fullscreen element needs appropriate `max()` expression based on its intent.

### Risks and Mitigations

- **Risk:** Developers forget to include `viewport-fit=cover` (ADR-076), and Safe Area variables don't activate.
  - **Mitigation:** ADR-076 is mandatory; code review gates it. Document the dependency between ADR-076 and ADR-081.

- **Risk:** Developers use `env(safe-area-inset-bottom)` without `max()`, breaking padding on devices without notches.
  - **Mitigation:** Document the `max()` pattern explicitly in this ADR and in code examples. Code review should flag bare `env()` usage.

- **Risk:** Fullscreen overlays (modals, canvas editor) forget to apply top Safe Area, and content is clipped by status bar.
  - **Mitigation:** ADR-072 (canvas editor) specifies all four insets. Modals should follow the same pattern.

- **Risk:** Landscape layout content extends into rounded corner zones on the left/right.
  - **Mitigation:** Apply left/right Safe Area padding to fullscreen elements and horizontal scrollers. Test in landscape on target devices.

## Related Decisions

- **ADR-076 (Mobile-First Viewport and Meta Tag Standard)** — Mandates `viewport-fit=cover` which activates `env(safe-area-inset-*)` values. Prerequisite for this ADR.

- **ADR-002 (Every PWA Must Link shared/safe-bottom.css)** — Provides a shared stylesheet applying `max(0px, env(safe-area-inset-bottom))`. Works in concert with this ADR for bottom Safe Area.

- **ADR-003 (Long Lists Must Scroll the Root Document)** — Companion mobile layout rule. Long lists scrolling the root document interact correctly with Safe Area padding on sticky chrome.

- **ADR-072 (Proof Image Capture and Annotation Standard)** — Canvas editor must apply all four Safe Area insets since it covers the entire viewport. Specific implementation of this ADR.

- **ADR-004 (Maps Strategy)** — Fullscreen maps must apply Safe Area insets to ensure controls and content don't hide behind notches or rounded corners.

## References

- MDN Web Docs: [Safe Areas (viewport-fit)](https://developer.mozilla.org/en-US/docs/Web/CSS/env#values)
- CSS Tricks: [The Notch and CSS](https://css-tricks.com/the-notch-and-css/)
- WebKit Blog: [Designing Websites for iPhone X](https://webkit.org/blog/7929/designing-websites-for-iphone-x/)
- W3C CSS Module Level 4: [env() Custom Properties](https://drafts.csswg.org/css-env-1/)
- `/var/www/360lm/shared/safe-bottom.css` — Implementation reference
- `/var/www/360lm/docs/adr/ADR-076-mobile-first-viewport-standard.md` — Viewport meta tag standard
- `/var/www/360lm/docs/adr/ADR-002-safe-bottom-css-mandatory.md` — Shared Safe-Bottom CSS
- `/var/www/360lm/docs/adr/ADR-072-proof-image-capture-annotation.md` — Canvas editor with Safe Area insets
- Memory note: `feedback_safe_bottom.md`, `feedback_mobile_scroll.md` — Original rule capture
