Safari & Chrome Mobile Keyboard Fix
A comprehensive fix for the notorious virtual keyboard bug that affects all web chats (Telegram Web, WhatsApp Web, etc.) in Safari, and viewport issues in Chrome Mobile. Inspired by Yandex Messenger’s UX solution.
The Problem
Safari Issue
When users type in a chat input on iOS Safari:
- Virtual keyboard opens
- User scrolls the page (not the chat)
- Input remains focused with keyboard open
- Page content becomes inaccessible behind keyboard
- No built-in way to dismiss keyboard
Chrome Mobile Issue
Virtual keyboard resizes viewport, breaking 100vh layouts in chat interfaces.
The Solution
Two-part fix:
- Safari: Detect page scroll while keyboard is open → auto-blur input
- Chrome: Use
interactive-widget=resizes-contentmeta tag
Installation
1. Chrome/Android Fix (Meta Tag)
Add to your <head>:
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no interactive-widget=resizes-content"/>2. Safari/iOS Fix (JavaScript)
Import and run globally in your main script:
import './safari-keyboard-viewport-fix.js'Or include directly:
<script src="safari-keyboard-viewport-fix.js"></script>How Safari Fix Works
- Detection: Monitors
visualViewport.resizeevents - Keyboard State: Tracks if INPUT/TEXTAREA is focused
- Scroll Detection: Compares
offsetTopchanges (throttled) - Auto-blur: Dismisses keyboard if page scrolls while keyboard is open
This mimics Yandex Messenger’s behavior - the only major chat app that solved this correctly.
Source Code
chrome-keyboard-fix.html (Meta tag)
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no interactive-widget=resizes-content"/>safari-keyboard-viewport-fix.js (Complete solution)
document.addEventListener('DOMContentLoaded', () => { if (isSafari() && window.visualViewport) { let { getValue: getOffsetTop, setValue: setOffsetTop } = useThrottledValue( window.visualViewport.offsetTop, 100, ) let keyboardIsOpen = false
const hideKeyboardOnScrollWindow = (e) => { const windowIsScrolled = window.visualViewport.offsetTop !== getOffsetTop()
if (keyboardIsOpen && windowIsScrolled) { document.activeElement.blur() } }
window.visualViewport.addEventListener('resize', (e) => { const isInputFocused = ['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName)
if (isInputFocused && !keyboardIsOpen) { keyboardIsOpen = true window.addEventListener('touchmove', hideKeyboardOnScrollWindow) window.addEventListener('scroll', hideKeyboardOnScrollWindow) } if (!isInputFocused) { keyboardIsOpen = false window.removeEventListener('touchmove', hideKeyboardOnScrollWindow) window.removeEventListener('scroll', hideKeyboardOnScrollWindow) } setOffsetTop(window.visualViewport.offsetTop) }) }
function isSafari() { const userAgent = navigator.userAgent return userAgent.includes('Safari') && !userAgent.includes('Chrome') }
function useThrottledValue(initialValue, delay) { let rawValue = initialValue let throttledValue = initialValue let timeoutId = null
const updateThrottledValue = () => { throttledValue = rawValue }
return { setValue: function (newValue) { rawValue = newValue if (!timeoutId) { updateThrottledValue() timeoutId = setTimeout(() => { timeoutId = null }, delay) } }, getValue: function () { return throttledValue }, } }})Why This Matters
- ✅ Fixes Telegram Web, WhatsApp Web, Discord Web in Safari
- ✅ Prevents broken UX with stuck keyboards
- ✅ Restores normal chat scrolling behavior
- ✅ Zero dependencies, vanilla JavaScript
- ✅ Battle-tested solution (4+ hours of research)
Affected Platforms
- Safari iOS: ❌ Broken (fixed by this script)
- Chrome Android: ❌ Viewport issues (fixed by meta tag)
- Yandex Messenger: ✅ Already has this fix built-in
Technical Details
Research Time: ~4 hours
Inspiration: Yandex Messenger’s UX implementation
Complexity: Handles visualViewport API edge cases
Performance: Throttled to 100ms for smooth scrolling