« All posts

iOS standalone PWA: keeping a chat composer above the keyboard

iOS PWA composer hidden by keyboard: interactive-widget is Chromium-only, dvh doesn't react, and a visualViewport-based CSS workaround is explored.

A developer building a chat screen inside an installed (add-to-home-screen) PWA on iOS ran into a familiar problem: when the on-screen keyboard opens, the message composer ends up hidden behind it. The commonly cited fix, the interactive-widget=resizes-content meta directive, turns out to be Chromium-only — WebKit ignores it entirely, and 100dvh doesn't respond to the keyboard either.

Detailed measurements expose why naive keyboard-detection logic fails: window.innerHeight already tracks the visual viewport once the keyboard is up, so any detection based on comparing it to visualViewport.height never fires. documentElement.clientHeight stays constant and isn't the containing block for fixed elements. The visualViewport resize event also fires early, roughly 200ms ahead of the actual keyboard animation, making it impossible to sync a counter-animation with the native slide.

Attempts included offsetting a fixed composer by a computed keyboard height, translating both container edges together, counter-animating the transition, and locking document scroll — the last of which backfired, causing iOS to overlay the keyboard instead of sliding the viewport. Using scrollIntoView() was actively harmful since it can't move a fixed element and instead scrolled the document, undoing iOS's own viewport shift. The current workaround publishes visualViewport.height to a CSS custom property (--vvh) and lets the layout root shrink to fit, manually replicating what Chrome's interactive-widget=resizes-content already does. It's a useful reference for engineers hitting the same WebKit gap between visual viewport APIs and reliable, animation-synced keyboard-avoidance behavior.