flutter_inappwebview (iOS): HTML fixed header moves/judders when the software keyboard opens — `resizeToAvoidBottomInset` true vs false
03:19 13 Jul 2026

Environment

  • Flutter 3.22.3, flutter_inappwebview: ^6.1.5
  • iOS 13+, Android (windowSoftInputMode="adjustResize")
  • A React SPA (React Router) is loaded inside a single full-screen InAppWebView. All inputs are HTML s inside main.content.

Goal

Full-screen webview app with a fixed header. When an input is focused, the header must stay fixed and only main.content should scroll while the focused input is brought above the keyboard.

App (Flutter)

Scaffold(
  resizeToAvoidBottomInset: /* true or false — each has a different problem, see below */,
  body: SafeArea(
    top: false,
    bottom: Platform.isAndroid,
    child: InAppWebView(
      initialUrlRequest: URLRequest(url: WebUri(webUrl)),
      initialSettings: InAppWebViewSettings(transparentBackground: true),
    ),
  ),
)

Web layout (React, inside the webview)

Mount / DOM hierarchy:

#root  (main.tsx: createRoot(#root).render())
└ App → AppProvider → AppInitProvider → AppRouter
   └ }>          // after auth branch, always renders MainLayoutContent
      └ MainLayoutContent
         └ div.layout                          (MainLayout.module.scss)
            ├ div.content [.withBottomNav?]     // wraps the 
            │  └  → page → PageLayout
            │     └ div.layout                  (PageLayout.module.scss)
            │        ├ {header} = 
// optional slot (Header.module.scss) │ ├ main.content // ★ the ONLY scroll area │ └ {footer} // optional slot (e.g., BottomButton) └ BottomNavBar // only on Home / Report / Profile / ...

Pages use PageLayout as their root. Pages that need a header pass header={

}; full-screen pages (e.g. Home) omit it.

Per-layer styles (current values):

Layer File Key styles
html, body, #root global.scss height: 100dvh; overflow: hidden
.layout (MainLayout) MainLayout.module.scss display:flex; flex-direction:column; height:100%
.content (MainLayout) flex:1; overflow:hidden; min-height:0; .withBottomNav → bottom safe-area (58px)
.layout (PageLayout) PageLayout.module.scss display:flex; flex-direction:column; height:100%; overflow:hidden
main.content (PageLayout) flex:1; overflow-y:auto; min-height:0; -webkit-overflow-scrolling:touchonly scroller
.header Header.module.scss position:sticky (no top!); z-index:101; flex-shrink:0; min-height:64px; padding-top: max(8px, safe-area-inset-top)
viewport index.html width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover

Note: the viewport has user-scalable=no, so iOS input auto-zoom is already disabled — the judder is not a zoom issue.

Behavior summary

resizeToAvoidBottomInset: true resizeToAvoidBottomInset: false
Android Works correctly: header stays fixed, only main.content scrolls When keyboard is open, scrolling moves the header together with the content
iOS Header judders on keyboard open and when switching focus between inputs; after closing the keyboard the area behind it takes time to repaint No juddering, but same as Android — scrolling moves the header together with the content

With resizeToAvoidBottomInset: true

  • Android: works exactly as intended.
  • iOS: broken:
    1. Header judders/shakes when the keyboard opens.
    2. Header also judders when moving focus from one input to another.
    3. After the keyboard closes, the region behind it takes a noticeable time to repaint (blank/background lingers before the web content fills back in).

With resizeToAvoidBottomInset: false

  • No focus juddering.
  • But on both Android and iOS, once the keyboard is open, scrolling moves the whole page including the header instead of scrolling only main.content.

What I've tried (for the false case)

  • main.content { padding-bottom: } where keyboardHeight = window.innerHeight - window.visualViewport.height.
  • Pin the top-level scroll to 0 so the header can't move:
window.addEventListener('scroll', () => { if (window.scrollY) window.scrollTo(0, 0); });

and/or Flutter:

onScrollChanged: (c, x, y) { if (Platform.isIOS && y > 0) c.scrollTo(x: 0, y: 0, animated: false); }

This helps, but there's residual jitter because WKWebView's scroll-to-focus is synchronous while the clamp (especially over the JS↔native bridge) runs a frame later. disableVerticalScroll: true doesn't help — in this plugin it only clamps user-initiated scrolls, not the programmatic focus scroll.

Question

  1. For a full-screen WKWebView, which resizeToAvoidBottomInset value is the right foundation?
  2. With true, how do I stop the iOS header juddering (on keyboard open and on focus switch) and the slow repaint after the keyboard closes?
  3. With false, how do I keep the fixed header from scrolling with the content when the keyboard is open (only main.content should scroll), ideally without bridge-induced jitter?
android ios reactjs flutter webview