Skip to content

Replace isPwa-conditional bottom-padding with proper safe-area-inset / dvh layout #1081

Description

@orveth

Context

PR #1078 (/scan paste button placement) and PR #1032 (home-screen buttons) both reach for the same pattern to handle different padding on PWA vs non-PWA mobile:

const isPwa = useIsPwa();
// ...
<div className={cn('flex w-72 flex-col gap-4', isPwa && 'pb-20')}>

This is a workaround. Per Josip on #1078: "approved but something smells here. I am fairly confident that this can be done better. lets just add some ticket so we don't forget this hack. problem with hacks like this is that most of the time it has other issues on different devices/sizes or bites you when you try to do something else in the future." gudnuf agrees the current shape is fragile.

Why it's a hack

  • useIsPwa() is a runtime media query (display-mode: standalone). That's a behavioral signal ("is this an installed PWA?"), not a layout signal. We're using it as a proxy for "how much vertical space does the browser/system chrome already leave us at the bottom?" — which is what we actually care about.
  • The pixel offset (pb-20 = 80px) is a guess based on visible iPhone X+ models. It doesn't track:
    • Other notched form factors that ship a different safe-area inset
    • Android PWAs (no home indicator → 80px is too much)
    • iPad PWA installed, landscape orientation
    • Future devices
  • It also doesn't help non-PWA mobile browsers where individual browser chromes (Safari URL bar, Chrome bottom toolbar, Brave bottom toolbar) carve up the visible viewport differently. PR fix(scan): float paste button over fullscreen scanner #1078 ended up swapping h-screenh-dvh on the scanner for the same reason — and we should be consistent about which viewport units we trust everywhere.

Proper fix(es) to consider

  1. env(safe-area-inset-bottom) for the bottom padding. Resolves to the real OS-reported safe-area inset; 0 in mobile browsers (already handled by the browser's own chrome), the actual home-indicator height on iOS PWA, the real navigation-bar height on Android PWA. We tried this on a fixed-positioned button mid-PR; the right shape is probably padding-bottom: max(theme(spacing.2), env(safe-area-inset-bottom)) on the in-flow container, not on a fixed element.
  2. 100dvh / 100svh / 100lvh consistently. The dvh switch on QRScanner should likely propagate to Page so the whole layout reasons in the dynamic viewport instead of mixing h-dvh (Page) with h-screen (scanner).
  3. Drop the PWA/non-PWA branch entirely — the visible difference should fall out of (1) + (2) automatically, with no useIsPwa reads on layout code.

Files touching this pattern today:

Acceptance criteria

  • Visually correct paste / Send-Receive-Buy placement on:
    • iOS Safari (URL bar visible + collapsed)
    • iOS Brave (URL bar visible + bottom toolbar)
    • iOS Chrome (taller bottom toolbar)
    • iOS PWA (with home indicator)
    • Android Chrome
    • Android PWA
    • Desktop Safari / Chrome (already works via sm:)
  • No isPwa && conditionals in the layout code afterward (or, if useIsPwa is kept for something else, none on layout/positioning).

Out of scope

  • Re-architecting Page / PageContent / PageFooter beyond what's needed to make these two screens correct.
  • Anything camera-related on QRScanner itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions