Keyboard slams open on your iPhone. That fixed input bar at the bottom? Gone, swallowed whole. You’ve swapped 100vh for dvh, chased the dynamic viewport height dream. But here you are, cursing Safari again.
CSS dvh burst onto the scene to slay the infamous 100vh mobile bug—URL bars that yo-yo, layouts that jump. It promised sanity: height units that flex with the browser chrome. Except.
It pretends the on-screen keyboard doesn’t exist.
Why Does CSS dvh Completely Ignore the Mobile Keyboard?
Blame the spec. CSS viewport units treat virtual keyboards as overlays, not layout shrinkers. dvh, svh, lvh—they all hold steady when keys deploy. No resize. Your position: fixed elements? Covered. Brutally.
The CSS viewport units spec treats the virtual keyboard as an overlay — it doesn’t resize the layout viewport. So dvh, svh, and 100vh all stay the same value when the keyboard opens.
That’s straight from the trenches. And it’s why millions of PWAs, chats, forms—anything with bottom-fixed inputs—still break on mobile.
But wait. Platforms feud here. iOS Safari? Scrolls the visual viewport up—visualViewport.offsetTop jumps, resize and scroll fire on visualViewport, but not window.resize. Android Chrome? Shrinks window.innerHeight outright, fires window.resize, offsetTop stays zero.
Handle one? The other crumbles. Classic cross-platform hell.
How Do You Actually Calculate Keyboard Height?
window.innerHeight minus window.visualViewport.height. Boom—keyboardHeight. Simple math, right?
Except iOS’s scroll trick means you need heuristics. Track layout refs with width guards: orientation flips innerWidth, keyboards don’t. On Android, width stays put too—but offsetTop flags iOS.
Here’s the code whisperer:
const handleWindowResize = () => {
const vv = window.visualViewport
const currWidth = window.innerWidth
const widthChanged = currWidth !== layoutWidthRef.current
// iOS: vv.offsetTop > 0 when keyboard is open → skip
// Android: width unchanged when keyboard opens → skip
// True orientation/resize: width changes → update reference
if (!vv || (vv.offsetTop === 0 && widthChanged)) {
layoutHeightRef.current = window.innerHeight
layoutWidthRef.current = currWidth
}
}
Tested. Reliable. No width wobbles from keyboards.
This isn’t just theory—it’s battle-tested across iOS Safari and Android Chrome. Next.js App Router? SSR safe. Zero deps, 0.8KB gzipped. React 17+, TS-typed, 19 Vitest specs.
Enter use-dynamic-viewport: The Tiny Fix That Just Works
npm install use-dynamic-viewport. One hook. Two CSS vars on :root: –dvh (true dynamic height), –keyboard-height.
import { useDynamicViewport } from 'use-dynamic-viewport'
export function Layout() {
useDynamicViewport() // magic
return <div className="app">...</div>
}
.app { height: var(–dvh, 100svh); }
.input-bar { position: fixed; bottom: var(–keyboard-height, 0px); left: 0; right: 0; }
Keyboard up? Bar floats perfectly above. Down? Full height. No JS polling, no ResizeObserver hacks.
Or snag JS values:
const { viewportHeight, keyboardHeight, isKeyboardOpen } = useDynamicViewport()
Chats, modals, fullscreens—fixed.
Look, browser vendors hyped dvh as the 100vh killer. Fair. But ignoring keyboards? That’s like fixing the door lock while leaving the window wide open. We’ve seen this movie: vh units born from desktop assumptions, mangled on mobile. Now viewport 2.0 repeats the sin.
My bet? CSS@keyboard-height units by 2026. Specs lag; hooks like this bridge the gap. Open source beats waiting—rl0425 dropped this on GitHub (https://github.com/rl0425/use-dynamic-viewport), npm live. Fork it, ship it.
But here’s the deep cut: this exposes viewport architecture’s core fracture. Layout viewport (for CSS) vs. visual viewport (what users see). Browsers patch inconsistently—iOS overlays and scrolls, Android resizes. Until the spec mandates keyboard-aware units, we’re gluing heuristics. Genius, fragile.
Why does it matter? Mobile’s 60% of web traffic. Forms convert or bounce on keyboard UX. PWAs die here. Devs waste weeks on ‘resize’ listeners that flake.
Is use-dynamic-viewport Safe for Production?
19 tests say yes. Handles edge: landscape keyboards, split-view iPads, Android notch drama. No globals fouled. SSR? Hydrates clean—runs client-only.
Prod apps already? Stack Overflow chats, Telegram web—imagine if they’d had this years ago.
Skeptical? Clone the repo. Vitest + RTL. Fire up emulators. Keyboard spam it.
This hook isn’t hype—it’s the pragmatic stab at sanity in a spec that’s half-baked.
Unique angle: remember IE’s hasLayout hackarounds? This is 2024’s viewport equivalent. Browsers evolve slow; open source sprints ahead. Grab it before dvh2 drops.
Why Hasn’t the CSS WG Fixed This Yet?
Politics. Interop demands consensus—Apple, Google, Mozilla align? Glacial. Meanwhile, 100vh zombies roam; dvh joins the undead.
Prediction: visualViewport API stabilizes, then native vars. But don’t hold breath.
🧬 Related Insights
- Read more: GitLab’s AI Prompts Promise Faster Shipping — But Who’s Really Winning?
- Read more: Python 3.14.1: 558 Bug Squashes and a Farewell to PGP Signatures
Frequently Asked Questions
What is CSS dvh and why does it fail on keyboards?
CSS dvh tracks browser chrome like URL bars but treats keyboards as overlays, keeping height fixed while your UI gets covered.
How do I fix CSS dvh keyboard issues in React?
Install use-dynamic-viewport npm package, call the hook—it injects –dvh and –keyboard-height vars automatically.
Does use-dynamic-viewport work on both iOS and Android?
Yes, handles platform diffs with offsetTop and width heuristics; tested across Safari and Chrome.
Is there a vanilla JS version?
Core logic’s in the repo—extract the resize handler; hook’s React wrapper.