Files
HaBit/docs/superpowers/specs/2026-05-24-app-shell-layout-design.md
Harkamal Randhawa 91cced4c75 Fix app shell layout with stable top bar and dynamic nav offset.
Unified chrome keeps the home logo top-left, measures bottom nav height
for scroll clearance, and adds responsive content widths for mobile/desktop.
2026-05-24 04:26:09 -06:00

2.8 KiB

App Shell & Responsive Layout Design

Date: 2026-05-24
Status: Approved for implementation
Approach: Unified app chrome + dynamic nav inset (Approach 2)
Desktop nav: Option A — bottom bar on all screen sizes

Problem

  1. Page content and overlays appear under the fixed bottom nav because clearance uses a static --bottom-nav-height: 6rem that does not match the real rendered nav height (safe-area insets, label toggling, active-state transforms).
  2. The home logo in AppPageHeader sits top-right and shifts vertically/horizontally as page titles vary in length.
  3. Layout is mobile-first only; desktop gets narrow centered columns with no breakpoint strategy.

Solution

App chrome structure

  • AppTopBar: Fixed top bar with HomeLogoLink always top-left; consistent height via --app-topbar-height.
  • AppPageHeader: Content-only title/description block inside main — no logo.
  • NavOffsetProvider: ResizeObserver on bottom nav sets --app-nav-offset on document.documentElement.
  • Overlays: Toasts and reminder banner use bottom: calc(var(--app-nav-offset) + offset).

Responsive (Option A)

Breakpoint Content width
default max-w-lg
md max-w-2xl
lg (standard pages) max-w-4xl
lg (wide: shop, customize) max-w-6xl

Bottom nav remains on all sizes, centered in max-w-6xl.

CSS variables

  • --app-topbar-height: 3.5rem — fixed top chrome height
  • --app-nav-offset: 0px — set dynamically by NavOffsetProvider; fallback 6rem in CSS
  • --bottom-nav-height — retained as fallback default for --app-nav-offset

Files

  • components/layout/app-top-bar.tsx (new)
  • components/layout/nav-offset-provider.tsx (new)
  • components/layout/app-page-header.tsx — remove logo
  • components/layout/app-shell.tsx — top bar + responsive widths
  • components/layout/app-nav.tsx — forwardRef for measurement; contain active transform
  • components/layout/global-bottom-nav.tsx — wrap with NavOffsetProvider
  • components/avatar/avatar-customize-layout.tsx — same shell pattern
  • app/(app)/layout.tsx — dynamic padding classes
  • app/globals.css — CSS vars and utility classes
  • components/ui/toast-provider.tsx — use --app-nav-offset
  • components/reminders/in-app-reminder-banner.tsx — use --app-nav-offset

Edge cases

  • Nav hidden (logged out / onboarding): --app-nav-offset reset to 0px.
  • Safe-area: measured offsetHeight includes safe-area padding automatically.
  • Home page /avatar: logo visible; optional aria-current="page" when active.

Testing

  • Scroll to bottom on Shop, Habits, Profile — content fully visible above nav.
  • Trigger toast + reminder — no overlap with nav.
  • Logo position stable across Avatar, Shop, Habits, Customize.
  • Viewports: 375px, 768px, 1280px.