What is the most reliable way to keep an iOS PWA chat composer above the keyboard?

0
0
Asked By MellowPine42 On

I'm building a chat screen for a standalone iOS PWA. When the software keyboard opens, a composer inside a fixed-height chat shell remains behind the keyboard. Chromium's interactive-widget=resizes-content does not appear to affect WebKit, and 100dvh has also seemed unreliable in my testing.

The shell currently uses position: fixed with top and bottom edges, while the messages area grows with flexbox and the composer stays at the bottom. On the device I tested, window.innerHeight follows the visual viewport, document.documentElement.clientHeight stays constant, and visualViewport.resize fires before the keyboard's visible movement finishes. The fixed containing block appears to correspond to visualViewport.height plus visualViewport.offsetTop.

I tried calculating a keyboard inset from visualViewport.height, translating the shell, counter-animating the movement, locking the document, and calling scrollIntoView(). The calculated inset works, but resizing the shell causes content such as a canvas to be laid out again. Locking the document prevents the viewport from sliding, and scrollIntoView() scrolls the document instead of reliably moving a fixed element.

The current pure-web approach I'm considering is publishing visualViewport.height to a CSS custom property and making the root layout that height:

const vv = window.visualViewport;
const publishHeight = () => document.documentElement.style.setProperty('--vvh', `${Math.round(vv.height)}px`);
vv.addEventListener('resize', publishHeight);
vv.addEventListener('scroll', publishHeight);
publishHeight();

Then the app uses a normal flex column rather than a fixed shell, with the messages area growing and the composer as the final child. Is this still the most dependable approach for standalone iOS PWAs? Can current WebKit reliably resize a fixed panel using 100dvh instead? Is there any way to obtain the keyboard's animation duration or easing curve, and is there a reason to prefer a fixed shell with a computed inset over shrinking the root layout? Finally, can overflow:hidden on the root cause problems, especially when it removes the document's scroll range?

4 Answers

Answered By CedarOrbit19 On

Before committing to JavaScript, test a panel whose height is explicitly based on 100dvh rather than using bottom: 0. For example, keep the header at a known height and set the chat panel to height: calc(100dvh - var(--header-height)); make it a flex column, and keep the composer as its final flex child. On WebKit versions where dynamic viewport units respond to the keyboard, the panel will shrink naturally and the composer will follow it without a keyboard variable or custom animation.

Answered By LunaHarbor7 On

For a pure web implementation, publishing visualViewport.height and letting the app use a regular flex layout is the approach I would ship. It avoids guessing a keyboard inset and keeps the composer in normal document flow, so the messages area simply loses space while the composer remains visible. A fixed shell plus a calculated bottom offset can work, but it is more fragile when other content is sized relative to that shell, such as a canvas. Restrict overflow handling to the standalone app layout and test nested scrolling and scroll chaining carefully.

MellowPine42 -

That matches what I’m seeing. The main downside so far is a brief bounce during keyboard transitions, so I’m avoiding any extra animation in the height handler.

Answered By QuietMaple8 On

There is no dependable web API that exposes iOS’s keyboard animation curve or duration. visualViewport events can tell you about the resulting geometry, but they do not provide the native transition metadata. Avoid trying to counter-animate the movement; update layout in response to the viewport and let the browser handle the native transition. Also test safe-area behavior separately: safe-area-inset-bottom may drop while the keyboard is open, so caching the non-keyboard inset can prevent an unexpected padding change from looking like extra bounce.

Answered By AmberTrolley63 On

A fixed shell with a computed inset is mainly useful when the rest of the app must retain a stable size—for example, when a canvas or game surface should not be resized. In that case, calculate the visible bottom edge from visualViewport.offsetTop plus visualViewport.height, update the inset on resize and scroll, and do not animate the value yourself. For a normal chat interface, shrinking the root or a dvh-sized flex panel is simpler because the composer remains in flow and the message list gets the correct remaining height automatically.

Related Questions

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.