Visual viewport geometry as React state.

Read the visible browser region, viewport offsets, zoom, keyboard occlusion and safe-area geometry when application logic needs coordinates as data.

CSS owns layout. React Viewport exposes geometry to logic.

Zero runtime dependencies · SSR safe · React 18.3 / 19 · Alpha

const {
  layout,
  visual,
  keyboard,
  safeArea,
} = useViewport()
Measuring…
Visual height
Pending
Offset top
Pending
Scale
Pending
Keyboard
Pending
Bottom occlusion
Pending
Safe bottom
Pending
Layout viewport → Visual viewportMeasuring browser geometry…Solid: layout boundary. Dashed: visual boundary. They can coincide. Diagram scaled to fit; values are live.

If CSS solves it, don’t install React Viewport.

React Viewport is for application logic that needs viewport geometry as data. It is not a replacement for 100dvh, safe-area CSS or normal responsive layout.

Do I need this?

Can CSS solve the layout?
Use CSS: grid, flexbox, sticky positioning, 100dvh, env(), media and container queries.
Only need one occasional VisualViewport value?
Use window.visualViewport?.height directly.
Does React logic need shared reactive geometry?
useViewport() may be useful for a coherent snapshot across consumers.
ProblemCSSReact Viewport
Full-height layoutPreferred: 100dvhUnnecessary
Safe-area paddingPreferred: env()Unnecessary
Responsive stylingPreferred: queriesUnnecessary
Sticky / fixed footerUsually sufficientUsually unnecessary
Visual dimensions and offsets in React logicNot a reactive JS snapshotYes
Pinch zoom in coordinate algorithmsNot a JS algorithm inputvisual.scale
Keyboard occlusion as JS stateNot a React state sourcekeyboard
Safe-area insets in JS calculationsenv() needs measurementsafeArea
JS rendering budget / coordinate visibilityCannot make the JS decisionGeometry inputs for your algorithm

This compares CSS with a React abstraction. Native browser APIs already expose geometry; the library does not invent exclusive access to it.

Why not just use window.visualViewport?

If all you need is one visualViewport.height read, you probably should. React Viewport adds useSyncExternalStore integration, shared subscriptions per Window, an SSR-safe snapshot, layout and visual geometry, measured safe areas and a conservative keyboard abstraction.

A CSS-variable bridge can share that same normalized state with styles. It is unnecessary for recreating ordinary env() padding. Read the store architecture.

What it does not do: replace CSS, move UI automatically, manage focus, detect devices, universally expose a physical keyboard rectangle, or solve all mobile-browser quirks.

When application logic needs geometry

Rendering decisions

Adapt the optional content React renders to the visible region. A virtualizer or custom rendering engine needs a numeric height.

Explore the application example →

Geometry in. Rendering decision out.

This live example changes which results React creates. CSS owns their layout.

Measuring viewport…

Example policy: reserve 320px for search controls, allow 48px per row, cap at 8. This is a viewport-derived budget, not a measurement of this card.

ResultBudget.tsx · actual source

Open the Live Geometry Lab → Focus an input, scroll, rotate or zoom to observe real browser changes.

Start with CSS.

This composer requires no React Viewport. Browser-native layout is the recommended approach when it meets your requirements. If it solves your problem, stop here.

Open CSS Baseline →

One reactive snapshot

One browser store per Window → shared subscription → useSyncExternalStore → consistent React snapshots.

npm install @nipe-solutions/react-viewport

API snapshot
import { useViewport } from '@nipe-solutions/react-viewport'

export function ViewportReadout() {
  const viewport = useViewport()

  if (!viewport.ready || viewport.visual === null) {
    return <p>Measuring viewport…</p>
  }

  return (
    <p>
      Visible: {viewport.visual.width} × {viewport.visual.height}
    </p>
  )
}
API reference →

Preview the coordinate model

Layout viewport

The layout reference: window.innerWidth and window.innerHeight.

Visual viewport

The visible region, with layout-relative offsets, document-relative page coordinates and zoom scale.

Keyboard and safe area

Raw bottom occlusion and protected-edge measurements. Application policy decides whether and how to combine them.

Explore the concepts and simulator

Before “Shifted keyboard”: visual.offsetTop moves the visible region. Bottom occlusion is Math.max(0, layoutHeight - (visualOffsetTop + visualHeight)). Shrinkage alone is not proof of a keyboard.

Geometry simulator

LIVE BROWSER

One screen, four measured regions

Compare what changes in each browser state and why. Layout, visual viewport, keyboard occlusion, and safe area stay separate.

Initializing viewport measurement
Layout viewport
Pending
Visual viewport
Pending
Safe area
Pending
Bottom occlusion
Pending
Keyboard occlusion
PendingNo keyboard inferred
Bottom occlusionMath.max(0, layoutHeight - (visualOffsetTop + visualHeight))
View

Measured by the library in your current browser. Simulator controls do not change it. Keyboard status: pending

GeometryDemo.tsx · actual source

Browser evidence has boundaries

Automated evidence. Deterministic Chromium, Firefox and WebKit tests cover package geometry and website behavior.

Physical-device status. iPhone Safari and Android Chrome geometry testing remains pending. Automation is not physical keyboard or pinch-zoom verification.