loke.dev
Header image for Stop Using dvh for Mobile Forms: Why the interactive-widget Property Is the Only Way to Tame the Virtual Keyboard

Stop Using dvh for Mobile Forms: Why the interactive-widget Property Is the Only Way to Tame the Virtual Keyboard

Dynamic viewport units were supposed to be the mobile layout savior, but they fail to handle the one thing that actually breaks your forms: the resizing logic of the virtual keyboard.

· 5 min read

Dynamic viewport units (dvh) are the most overrated "fix" for mobile layouts in the last five years. We were promised a savior that would finally account for the shifting sands of mobile browser UI, but when it comes to forms, dvh is effectively a placebo. It solves the "browser navigation bar" problem while completely ignoring the 800-pound gorilla in the room: the virtual keyboard.

If you’ve ever built a mobile login page where the "Submit" button disappears behind the keyboard or, worse, weirdly floats halfway up the screen in a jittery mess, you know exactly what I’m talking about.

The dvh lie

We all jumped on the dvh (Dynamic Viewport Height) bandwagon because we were tired of the browser's address bar covering up our footers. And to be fair, 100dvh is great for a hero section. It calculates the height based on the *actual* visible area as the browser UI expands and contracts.

But here is the catch: In most browsers, the virtual keyboard is not considered a dynamic UI element that triggers a `dvh` resize.

When the keyboard slides up on an iPhone, the dvh value usually remains exactly the same. The keyboard just slides *over* your layout. If your "Save Changes" button was at the bottom of a 100dvh container, it’s now hidden under a grey slab of keys. You’re left with a broken user experience and a frustrated user who has to dismiss the keyboard just to find the button they were supposed to click.

The "Visual" vs "Layout" Viewport nightmare

To understand why this happens, you have to realize that mobile browsers have two viewports.

1. The Layout Viewport: The total area the browser uses to calculate things like width: 100%.
2. The Visual Viewport: The part of the page actually visible on the screen (which gets smaller when you zoom in or when the keyboard appears).

Standard CSS units like vh and dvh generally stick to the Layout Viewport. They don't care that the keyboard is currently obscuring the bottom 40% of the screen.

Meet the real hero: interactive-widget

A while back, the Chrome team (and eventually the W3C) realized that letting the browser "guess" how the keyboard should affect the layout was a bad idea. They introduced a property for the viewport meta tag called interactive-widget.

This property lets you explicitly tell the browser: "Hey, when the keyboard appears, I want you to actually resize my layout so I can move things out of the way."

How to use it

You don't put this in your CSS. It goes right into your HTML <head> inside the existing viewport meta tag.

<!-- The old, broken way -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<!-- The "I want my forms to actually work" way -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content">

By adding interactive-widget=resizes-content, you are telling the browser to shrink the Layout Viewport when the keyboard pops up.

The Three Flavors of Keyboard Chaos

There are three values you can use for interactive-widget. Choosing the right one depends on how much you trust your CSS:

1. `resizes-visual`: The default behavior on many browsers. The layout stays the same, but the "visible" area shrinks. This is why your footers get covered.
2. `resizes-content`: This is the magic one. It shrinks the actual layout viewport. Your 100vh (or 100dvh) will now represent the space *above* the keyboard.
3. `overlays-content`: The keyboard just floats over everything without triggering any resize logic at all.

A Practical Example: The Sticky Footer

Imagine a chat application. You want the input field to stay pinned to the bottom of the screen. Without interactive-widget=resizes-content, the keyboard will cover the input as soon as the user taps it.

With it, your layout adjusts automatically. Here is how I usually structure a full-screen form now:

/* This container will now actually shrink when the keyboard appears */
.form-container {
  display: flex;
  flex-direction: column;
  height: 100dvh; /* dvh handles the URL bar */
  justify-content: space-between;
}

.scrollable-content {
  flex: 1;
  overflow-y: auto;
}

.sticky-action-bar {
  padding: 1rem;
  background: white;
  border-top: 1px solid #ddd;
}
<div class="form-container">
  <div class="scrollable-content">
    <!-- Lots of inputs here -->
    <input type="text" placeholder="Type something...">
  </div>
  
  <footer class="sticky-action-bar">
    <button>Submit Post</button>
  </footer>
</div>

If you have the resizes-content meta tag enabled, the .sticky-action-bar will jump up and sit perfectly on top of the keyboard the moment it appears. No jerky JavaScript window.visualViewport listeners required.

The "Safari" Problem (Edge Cases)

I’d be lying if I said this was a perfect silver bullet. As of this writing, interactive-widget is well-supported in Chromium-based browsers (Chrome, Edge, Android Browser).

Safari is... being Safari. Apple has historically preferred to handle keyboard overlays in its own proprietary way (often by "sliding" the whole window up). However, using resizes-content doesn't break Safari; it just ignores it and falls back to its default behavior. By adding this property, you're making life 10x better for your Android users without hurting your iOS users.

Why you should switch today

Stop trying to fix keyboard issues with scrollIntoView() hacks or complex ResizeObserver logic. Most of the "jank" we see in mobile web forms comes from the browser and the OS fighting over who controls the scroll position.

By using interactive-widget=resizes-content, you are handing the control back to the CSS engine. It makes your dvh and vh units behave the way you actually expected them to behave in the first place.

Give it a shot on your next login page. Open it in Chrome on an Android device, tap an input, and watch your layout actually respond like a first-class citizen instead of a broken document.