Visual viewport geometry as React state.
CSS owns layout. React Viewport exposes geometry; your application decides when that geometry changes behavior. If CSS solves it, don’t install React Viewport.
const { ready, layout, visual, keyboard, safeArea, orientation, supported } = useViewport()Start with CSS alternatives, then read Keyboard and safe area and Browser behavior.
- CSS can size or position it: use CSS.
- You need one occasional value: read
window.visualViewportdirectly. - React must react to shared layout/visual geometry: use React Viewport.
Alpha software:
0.1.0-alpha.0may change. Physical iPhone Safari and Android Chrome testing is pending. Read browser limitations and real-device QA before making a support claim.
npm install @nipe-solutions/react-viewportThe package has no runtime dependencies. It supports React and React DOM
^18.3.0 || ^19.0.0, ships ESM, CommonJS, and TypeScript declarations, and
requires Node.js >=24 <25 for repository development.
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 size: {viewport.visual.width} × {viewport.visual.height}; keyboard:{' '}
{viewport.keyboard.open ? `${viewport.keyboard.height}px` : 'closed'}
</p>
)
}No provider is required. Use ViewportProvider only for a same-origin window scope after verifying
that an iframe or popup Window is accessible. Each window gets one shared useSyncExternalStore
store and listener set. See the API reference.
Use it directly for occasional reads. React Viewport earns its place when React needs a consistent, reactive snapshot shared across consumers, an SSR-neutral initial snapshot, safe-area values, or normalized keyboard occlusion. It exposes geometry; it does not position elements, manage focus, scroll automatically, define responsive breakpoints, or replace CSS.
Test React Viewport on your phone →
Change visual dimensions, offsets, page coordinates, scale, and safe areas; then test rendering budgets and document-coordinate visibility. Copy diagnostics excludes input text. Physical QA remains pending; follow the device protocol. A secondary CSS baseline remains available for layout work.
readybecomes true after the first client measurement. Before then,layout,visual, andorientationare null; false does not mean the browser APIs are unsupported.layoutis the layout viewport width and height fromwindow.innerWidthandwindow.innerHeight, in CSS pixels. It is the page's reference plane, not a promise that every point is visible or unobstructed.visualis the visible viewport's size, layout-relative offsets, document-relative page coordinates, and scale. It comes fromwindow.visualViewportwhen available; otherwise documented layout geometry is used. A visual change does not, by itself, identify its cause as a keyboard.keyboard.openrecords sufficient native or fallback evidence of an on-screen keyboard.keyboard.heightis only bottom-edge occlusion in CSS pixels, not the on-screen keyboard's full rectangle. Native floating geometry can therefore be open with a zero height.safeAreacontains the four raw CSSenv(safe-area-inset-*)measurements. It does not automatically become zero while a keyboard is visible. For a bottom constraint, useMath.max(keyboard.height, safeArea.bottom); do not add them.orientationis derived from the layout viewport aspect ratio. It is not a device-orientation sensor reading.supported.visualViewportandsupported.virtualKeyboardreport runtime API availability. They do not prove a behavior was physically tested, that overlay mode is active, or that a keyboard will be detected in every configuration.
The Layout viewport is window.innerWidth and window.innerHeight: the
coordinate space used for layout. The Visual viewport is the currently
visible region. When window.visualViewport is available, it also has offsets,
page coordinates, and a scale. A soft keyboard, browser UI, or pinch zoom can
change the visual viewport without changing the layout viewport.
The API intentionally keeps those coordinate systems separate:
const { layout, visual, keyboard, safeArea, supported } = useViewport()On a client without window.visualViewport, visual falls back to layout
geometry with zero offsets, page coordinates from window scroll, and scale 1.
supported.visualViewport records that this is fallback geometry rather than a
native VisualViewport reading.
visual.offsetTop and offsetLeft are layout-relative. visual.pageTop and
pageLeft are document-relative CSS pixels. Convert a DOM rectangle by adding
window.scrollX and window.scrollY to getBoundingClientRect() values from
the same window. Do not multiply by visual.scale; scale is not a breakpoint.
keyboard.height is the estimated or reported bottom viewport occlusion caused
by the software keyboard. It is not the physical keyboard's full rectangular
height.
Detection follows a deliberately short hierarchy:
- Native Virtual Keyboard intersection geometry is authoritative when available.
- Otherwise, conservative VisualViewport inference can report an occlusion.
- When the evidence is insufficient, the library reports no keyboard.
The W3C VirtualKeyboard API defines
boundingRect as the intersection of the virtual keyboard with the document
viewport in client coordinates. supported.virtualKeyboard means that API is
present; it does not mean overlaysContent mode is active. This library observes
geometry and never enables overlay mode. A non-empty native intersection sets
open: true; if floating geometry does not touch the layout viewport's bottom
edge, bottom occlusion remains height: 0.
A bottom-attached partial-width rectangle still yields a scalar bottom inset. That scalar cannot represent segmented or arbitrary-shape avoidance.
The fallback infers an occluding software keyboard only when an
editable element is focused, zoom is not active, and visual-bottom occlusion
crosses max(80 CSS px, 15% of layout height). The keyboard-closed baseline is
only an evidence gate: reported fallback height is always the current
Math.max(0, layoutHeight - (visualOffsetTop + visualHeight)). If layout and
visual height shrink together with no current bottom occlusion, the keyboard
remains closed. Focus alone never means that a software keyboard is open. This
deliberate heuristic can miss small, floating, or split keyboards; treat
keyboard as measured or inferred geometry, not a device-level keyboard
guarantee.
Keyboard occlusion and the raw bottom safe-area inset can describe the same
covered edge. When an application needs one bottom constraint, use
Math.max(keyboard.height, safeArea.bottom) rather than adding them.
For CSS-driven positioning, install variables on the document root (the default) or on a chosen element:
import { useViewportCssVariables } from '@nipe-solutions/react-viewport'
export function App() {
useViewportCssVariables()
return <main>…</main>
}The hook is a CSS bridge, not a layout system. It writes these client-side variables: layout and visual width/height,
visual offsets/page positions/scale, keyboard height, and four safe-area inset
lengths. Dimensional variables such as --react-viewport-layout-height are
removed until the first measurement, rather than populated with made-up server
values. Consumers share one store per window. CSS-variable ownership is restored on cleanup;
see Concepts.
Non-zero env(safe-area-inset-*) values generally require the page viewport to
opt into viewport-fit=cover. Configure that metadata before relying on the
package's measured safeArea values; unsupported or zero-inset environments
truthfully report zero.
For a secondary positioning recipe, see the
CSS baseline. Prefer direct CSS env() and
dynamic viewport units when they solve the layout.
SSR uses the stable, geometry-neutral snapshot required by useSyncExternalStore without accessing browser globals.
Render a placeholder until ready becomes true after hydration.
Prefer CSS when the browser can express the behavior directly. Use dvh, svh,
and lvh for viewport-relative sizing; use env(safe-area-inset-*) for safe
area padding; and use media/container queries for responsive layout. This
library is for React behavior that needs measured geometry or an explicit
layout-versus-visual distinction. It is not a breakpoint, device-detection,
scroll-locking, focus-management, modal, or general mobile-layout library.
Supported means the runtime can detect an API on the current browser. Tested means a deterministic repository scenario covers a behavior in the configured Chromium, Firefox, or WebKit projects. Fallback means the package uses documented alternate geometry when an optional API is absent. These labels are different from physical-device verification.
The project does not claim universal browser support. In particular:
- Browser APIs cannot reliably distinguish all floating or split software-keyboard arrangements.
- The fallback inference intentionally favors false negatives over moving UI for ordinary browser chrome changes.
- Desktop automation cannot reproduce physical mobile browser chrome or keyboard animations exactly.
- Embedded WebViews can expose different viewport behavior and need host-level verification.
- Foldable viewport segments and synthetic keyboard animations are outside v1.
See docs/browser-notes.md for the browser-note
registry and docs/REAL_DEVICE_QA.md for the current
physical-device matrix.
- Repository: https://github.com/NIPE-Solutions/react-viewport
- Security reporting:
SECURITY.md - Contributing:
CONTRIBUTING.md - Changelog:
CHANGELOG.md - Release readiness:
0.1.0-alpha.0 - License: MIT