Precision runtime helper that computes the visible viewport height on real devices and in‑app browsers. Exposes --svh and --lvh so you don’t rely on broken CSS vh. Built for art‑directed, design‑intent layouts that must stay visually consistent across display modes and app browsers. Initialize once to eliminate per‑environment viewport workarounds, reducing debugging overhead across Instagram, WhatsApp, WebViews, and iOS Safari.
- Provides reliable
--svhand--lvhCSS variables that behave correctly in in‑app browsers and WebViews. - Fixes broken
vhbehavior in environments like Instagram, WhatsApp, and generic WebViews. - Demo:
https://vanilla-dopesites.vercel.app/
- npm:
npm install vh-calculation-fix - yarn:
yarn add vh-calculation-fix
import { initViewportHeight } from 'vh-calculation-fix';
initViewportHeight();const { initViewportHeight } = require('vh-calculation-fix');
initViewportHeight();.hero {
min-height: var(--lvh);
display: grid;
place-items: center;
}Design reference: Figma https://www.figma.com/design/ymd9XlC1rq8OJIOJv7zNRV/DOPESITES?node-id=0-1&t=GdkJITg1mUZwE1nP-1
:root {
--sp-x-7: calc(7 / 1728 * 100vw);
--sp-x-13: calc(13 / 1728 * 100vw);
--sp-x-33: calc(33 / 1728 * 100vw);
--sp-x-42: calc(42 / 1728 * 100vw);
--sp-x-95: calc(95 / 1728 * 100vw);
--sp-x-109: calc(109 / 1728 * 100vw);
--sp-x-268: calc(268 / 1728 * 100vw);
--sp-y-7: calc(7 / 1117 * var(--lvh));
--sp-y-45: calc(45 / 1117 * var(--lvh));
--sp-y-95: calc(95 / 1117 * var(--lvh));
--sp-y-109: calc(109 / 1117 * var(--lvh));
--sp-y-120: calc(120 / 1117 * var(--lvh));
--sp-y-127: calc(127 / 1117 * var(--lvh));
--sp-y-268: calc(268 / 1117 * var(--lvh));
--fs-12: calc(12 / 1117 * var(--lvh));
--fs-13: calc(13 / 1117 * var(--lvh));
--fs-14: calc(14 / 1117 * var(--lvh));
--fs-15: calc(15 / 1117 * var(--lvh));
--fs-20: calc(20 / 1117 * var(--lvh));
--fs-logo: calc(128 / 1117 * var(--lvh));
}This library is about preserving original design intent across display modes and in‑app browsers. The goal is to keep heroes, sections, and spacing visually consistent when in-app scrolling alters the visible height.
Steel Cut shows px/rem‑only design shifting between macOS “Default” and “More Space” resolutions. Dopesites demonstrates how var(--lvh) or var(--svh) and a visible‑height‑driven spacing system preserve the same look across resolutions and under in‑app chrome.
- Use for design‑driven projects with art‑directed layouts and precise Figma specs.
- Use when your hero, sections, and typography spacing are part of the concept and must remain visually consistent.
- For projects where minor layout variance is acceptable, the native browser defaults may be sufficient.
- For teams delivering custom design layouts to clients or studios with exacting requirements, this eliminates time spent resolving
vhinconsistencies across in-app browsers.
- In‑app browsers overlay chrome (address bars, gesture bars, toolbars) that eats vertical space.
- Virtual keyboards shrink the visible area during input.
- Mobile Safari adjusts viewport on scroll, changing the visible height.
- CSS
vhignores these dynamics, so100vh≠ visually available height. - Symptoms: cropped heroes, content jumping, layouts that only break inside Instagram/WhatsApp/Telegram — this library addresses that by driving CSS with the actual visible height.
vwis tied to width while the core problem is vertical space disappearing under chrome/keyboard.- Faking vertical rhythm with
vwassumes aspect ratios and ignores the very chrome that breaks layouts. vwis great for horizontal sizing; it’s a poor default for section heights/vertical spacing.- For vertical rhythm (section heights, top/bottom spacing, type scale), use metrics derived from actual visible height — provided via
--svh/--lvhinvh-calculation-fix.
initViewportHeight();When to use:
- Hero sections that must truly fill visible height.
- Forms/pages with keyboards that would otherwise cause jumps.
- Routes that need consistent spacing across different app browsers.
import { useEffect } from 'react';
import { initViewportHeight } from 'vh-calculation-fix';
export function App() {
useEffect(() => {
initViewportHeight();
}, []);
return <div style={{ minHeight: 'var(--lvh)' }}>...</div>;
}Notes:
- Auto-switching: non in-app browsers that support native
svh/lvhuse those units; in-app/WebViews or unsupported browsers use pixel values. - Ensure your CSS uses
var(--lvh)orvar(--svh). - In SSR frameworks (Next.js), run initialization in
useEffect. - React Strict Mode double-invokes effects in dev; cleanup prevents duplicates.
When to use:
- App shells and layouts that must adapt to visible height.
- Pages with keyboard interactions or dynamic toolbars.
:root {
/* Example scale unit based on visible height */
--sp-y-127: calc(127 / 1117 * var(--lvh));
}
.section {
padding-block: var(--sp-y-127);
}
When to use:
- Section spacing, vertical rhythm, and type scales that should remain consistent across display modes.
--svh: safe viewport height based onwindow.innerHeightfor stable content sizing.--lvh: large/visible viewport height reflecting real on‑screen space usingvisualViewport.heightplus platform rules.- Both update on viewport changes so CSS scales fluidly with the actual visible height.
- On iOS,
--lvhis capped usingmin(visualViewport.height, innerHeight)to avoid overexpansion.
initViewportHeight(options?): initializes and subscribes to viewport changes, returns a cleanup function.setViewportHeight(options?): alias toinitViewportHeight.
type Options = {
forceInApp?: boolean;
};forceInApp: Force in‑app behavior (testing/debugging only).
- Variables aren’t updating — Ensure
initViewportHeight()runs once on page/app mount before styles that usevar(--lvh). visualViewportmissing — Library falls back toinnerHeight; layouts remain consistent on older devices.
Targets modern browsers that support visualViewport and gracefully falls back to innerHeight when unavailable. Explicitly handles in‑app browsers/WebViews (Instagram, WhatsApp, Telegram, generic WebViews) and includes iOS‑aware logic.
- Modern browsers (with
visualViewport) - Fallback to
innerHeightwhere needed - In‑app browsers/WebViews (Instagram, WhatsApp, Telegram, etc.)
npm run build: Emits ESM/CJS bundles and type definitions todist.npm run test: Builds then runs unit tests with Vitest.npm run lint: Lints the codebase with ESLint.npm run typecheck: TypeScript type checking withtsc --noEmit.
- No TypeScript required. Import ESM or require CJS from
vh-calculation-fix. - Use
initViewportHeight(options?)to set--svh/--lvh; options is a plain object. - Error messages are type‑agnostic and describe invalid option values.
- For in‑browser
<script type="module">, use the ESM import example above. - For Node/CommonJS bundlers, use the
requireexample above.





