Mobile keyboard covering your input or fixed footer? How to fix it on iPhone and Android
Why the phone keyboard hides the input or the Send button, why iPhone and Android get it wrong in different ways, and the fix for each.

Short answer: when the keyboard opens, iPhone and Android change different things. On iPhone the page keeps its size and only the visible area shrinks, so a position: fixed footer ends up behind the keyboard. On Android with interactive-widget=resizes-content, the page really shrinks and the fixed footer rides up with the keyboard, where it can land on top of the input. The fix is to pick a behavior in the viewport meta tag and track the keyboard height with visualViewport wherever that setting isn’t honored.
Two viewports
A mobile browser measures the screen twice:
- Layout viewport: the size of the page.
position: fixed,100vh,100dvh,innerHeightand height media queries all follow it. - Visual viewport: the part the user can see right now. It shrinks with pinch zoom and with the keyboard. JavaScript reads it from
window.visualViewport.
The keyboard always shrinks the visual viewport. The question is whether it shrinks the layout viewport too, and each platform answers differently.
On iPhone: the footer hides behind the keyboard
iOS never resizes the layout viewport for the keyboard, in any browser (they all run on WebKit). innerHeight and 100dvh stay the same; only visualViewport.height goes down. So:
- A footer or tab bar with
bottom: 0sits behind the keyboard. - A Send button that lives in a fixed footer disappears while the user types.
- iOS scrolls the page so the focused field shows, but only if the page can scroll. In app-like layouts with
body { overflow: hidden }and a fixed height, it pushes the whole screen up instead, fixed header included.
iOS ignores interactive-widget. Changing the viewport meta tag does nothing there.
On Android: it depends on interactive-widget
Since Chrome 108, Android defaults to the same model as iPhone: the keyboard only shrinks the visual viewport. The viewport meta tag changes that:
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
| Value | What the keyboard does |
|---|---|
resizes-visual (default) |
Shrinks only the visible area, like iPhone. The fixed footer stays behind the keyboard. |
resizes-content |
Shrinks the page. innerHeight, 100dvh and height media queries change, and the fixed footer moves up to the top of the keyboard. |
overlays-content |
Changes nothing. The keyboard just covers the page. |
resizes-content is what most chat UIs want: the message box sticks to the keyboard. It also creates the classic bug. If the thing fixed at the bottom is the tab bar rather than the message box, the tab bar rides up with the keyboard and sits on top of the input and the Send button. It looks fine at rest. The moment someone starts typing, they can’t see what they write.
The fix
1. Decide what stays pinned to the bottom
With the keyboard open, only what the user needs for typing should stay attached to it. Tab bars, banners and floating buttons get out of the way:
@media (pointer: coarse) {
body:has(input:focus, textarea:focus, [contenteditable]:focus) .tabs {
display: none;
}
}
This fixes Android with resizes-content and doesn’t hurt iPhone, where the tab bar would be behind the keyboard anyway. Don’t hide it by screen height (max-height: 500px): a phone in landscape with no keyboard matches that too.
2. Track the keyboard with visualViewport
Where the layout viewport doesn’t shrink (iPhone, and Android by default), read the keyboard height and lift what has to stay visible:
const vv = window.visualViewport;
function keyboard() {
const height = Math.max(0, window.innerHeight - vv.height - vv.offsetTop);
document.documentElement.style.setProperty('--keyboard', `${height}px`);
}
vv.addEventListener('resize', keyboard);
vv.addEventListener('scroll', keyboard);
keyboard();
.message-box {
position: fixed;
left: 0;
right: 0;
bottom: var(--keyboard, 0px);
}
On Android with resizes-content, innerHeight has already shrunk, the math comes out to zero, and the same code works on both platforms.
3. Give the page room to scroll
On a regular form, don’t lock body to 100vh with overflow: hidden. If the page can scroll, the browser brings the focused field above the keyboard on its own. If the field lives inside its own scroll container, call field.scrollIntoView({ block: 'center' }) on focus.
What doesn’t fix it
- Switching
position: fixedtoabsolute. The footer stops covering things, and also stops staying at the bottom of the screen. env(keyboard-inset-height)and the VirtualKeyboard API. Chromium only, and only afternavigator.virtualKeyboard.overlaysContent = true. iPhone doesn’t have the API.100dvhon its own. On iPhone it doesn’t change with the keyboard. On Android, only withresizes-content.
How to test it
Chrome’s device mode never opens a keyboard, which is why this bug ships so often: on a laptop you type on the physical keyboard and the screen never changes.
In Breakpane, tap a field on a phone and the keyboard slides up on the device screen with that platform’s rules: on iPhone the page keeps its size and only the visible area shrinks; on Android it follows the interactive-widget in your viewport meta tag. Open an iPhone and a Galaxy side by side and you see right away whether the Send button went behind the keyboard or the tab bar climbed on top of the input. Toggle it with ⇧⌘K (Ctrl+Shift+K on Windows).
Two limits: the iPhone case where the whole screen gets pushed up because the page can’t scroll isn’t reproduced, and the VirtualKeyboard API reports zero. If your layout depends on either, check once on a phone. For what the browser bar does while scrolling, see 100vh in iPhone Safari.
Checklist
- Viewport meta tag with an
interactive-widgetvalue chosen on purpose. - With the keyboard open, tab bars and floating buttons get out of the way.
- Input and Send button visible with the keyboard open, on an iPhone and an Android phone, portrait and landscape.
- The page can scroll, or the field calls
scrollIntoViewonfocus.


