@nipe-solutions/react-viewport
v0.1.0-alpha.0
Published
Reactive React viewport geometry for layout, visual viewport, keyboards, and safe areas.
Readme
@nipe-solutions/react-viewport
Reliable mobile viewport state for React.
@nipe-solutions/react-viewport exposes reactive layout viewport, visual
viewport, keyboard-occlusion, and safe-area geometry. It is intended for the
application behavior that CSS cannot express by itself, such as a composer that
must respond to a measured keyboard occlusion.
Alpha software:
0.1.0-alpha.0is an early release. Its API and browser behavior may change. Physical-device QA is still pending; seedocs/REAL_DEVICE_QA.md. Measured automated-release evidence and deployment status are recorded in the0.1.0-alpha.0readiness report.
Installation
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.
Quick start
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 for normal use. Use ViewportProvider only to scope a
subtree to an accessible, same-origin Window, such as an iframe or test
window. A cross-origin window cannot expose the document APIs this package
measures. Passing targetWindow={null} intentionally supplies the stable server
snapshot (ready: false) to descendants.
import { ViewportProvider } from '@nipe-solutions/react-viewport'
export function EmbeddedViewport({ childWindow }: { childWindow: Window | null }) {
return <ViewportProvider targetWindow={childWindow}>{/* descendants */}</ViewportProvider>
}Layout viewport versus visual viewport
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.
Keyboard state is conservative
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 geometry is authoritative when available.
- Otherwise, conservative VisualViewport inference can report an occlusion.
- When the evidence is insufficient, the library reports no keyboard.
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
max(0, layout.height - (visual.height + visual.offsetTop)). 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.
CSS variables
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>
}.composer {
position: fixed;
right: max(1rem, var(--react-viewport-safe-area-right, 0px));
bottom: calc(
var(--react-viewport-keyboard-height, 0px) +
max(1rem, var(--react-viewport-safe-area-bottom, 0px))
);
left: max(1rem, var(--react-viewport-safe-area-left, 0px));
}The hook 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. Multiple live hooks coordinate ownership per target property: the
last-mounted owner supplies values, and the last owner to leave restores the
latest consumer value observed before a library write. A stable ref target may
start null, be replaced, or detach; ownership and subscriptions migrate after
the corresponding React commit without stale writes to the previous element.
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.
SSR and hydration
Server rendering is safe: importing the package and calling useViewport do not
access browser globals. The stable server snapshot has ready: false, null
layout and visual values, a closed zero-height keyboard, zero safe-area insets,
and false support flags. Render a safe placeholder for geometry-dependent UI
until ready is true to avoid assumptions during SSR and hydration.
When CSS is enough
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.
Browser terminology and limitations
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 every floating or split keyboard.
- 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.
Project
- 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
