npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, πŸ‘‹, I’m Ryan HefnerΒ  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you πŸ™

Β© 2026 – Pkg Stats / Ryan Hefner

viewportify

v1.0.0

Published

πŸ–₯️ The Ultimate Cross-Platform Viewport Toolkit - Consistent viewport dimensions across iOS, Android, Windows & all browsers. Solves 100vh issues, supports svh/lvh/dvh, keyboard detection, safe areas & more!

Readme

πŸ–₯️ Viewportify

The Ultimate Cross-Platform Viewport Toolkit

npm version bundle size TypeScript License: MIT

Stop fighting with window.innerHeight on iOS! Viewportify provides consistent, accurate viewport dimensions across all platforms and browsersβ€”including the infamous iOS Safari toolbar issue.

✨ Why Viewportify?

| Problem | Viewportify Solution | |---------|---------------------| | 100vh is broken on iOS Safari | βœ… Accurate 100vh value that accounts for toolbar | | window.innerHeight changes on scroll | βœ… Stable svh, lvh, dvh measurements | | No native svh/lvh/dvh support | βœ… JavaScript polyfill with CSS variables | | Keyboard detection on mobile | βœ… Built-in keyboard visibility tracking | | Safe area insets for notches | βœ… Easy access to safe area values | | Responsive breakpoints | βœ… Tailwind-style breakpoint system | | React integration | βœ… Ready-to-use hooks | | Need width/height on resize | βœ… useWindowSize() hook updates on resize | | Measure element by ref | βœ… useElementSize(ref) returns exact dimensions | | Non-React resize listener | βœ… onWindowResize() with debounce support |

πŸš€ Quick Start

npm install viewportify
import { getHeight, getWidth, isMobile, isIOS } from 'viewportify';

// Get accurate viewport dimensions (works on iOS!)
console.log(getWidth());   // 390
console.log(getHeight());  // 844 (correct even with Safari toolbar!)

// Device detection
console.log(isMobile());   // true
console.log(isIOS());      // true

πŸ“¦ Installation

# npm
npm install viewportify

# yarn
yarn add viewportify

# pnpm
pnpm add viewportify

CDN (UMD)

<script src="https://unpkg.com/viewportify/dist/index.umd.min.js"></script>
<script>
  const { getHeight, isMobile } = Viewportify;
  console.log(getHeight());
</script>

πŸ“– API Reference

Core Viewport Functions

import {
  getWidth,           // Viewport width in pixels
  getHeight,          // Viewport height (accurate for iOS!)
  getInnerWidth,      // window.innerWidth
  getInnerHeight,     // window.innerHeight
  get100vh,           // Accurate 100vh value
  getSvh,             // Small viewport height (toolbar expanded)
  getLvh,             // Large viewport height (toolbar collapsed)
  getDvh,             // Dynamic viewport height
  getDPR,             // Device pixel ratio
  getViewport,        // Full ViewportInfo object
} from 'viewportify';

Device Detection

import {
  isMobile,           // true for phones
  isTablet,           // true for tablets
  isDesktop,          // true for desktops
  isIOS,              // true for iPhone/iPad
  isAndroid,          // true for Android devices
  isSafari,           // true for Safari browser
  isTouch,            // true if touch-capable
  isStandalone,       // true if running as PWA
} from 'viewportify';

Orientation & Keyboard

import {
  getOrientation,     // 'portrait' | 'landscape'
  isKeyboardVisible,  // true when keyboard is open (mobile)
  getKeyboardHeight,  // Estimated keyboard height in pixels
} from 'viewportify';

Safe Area & Scrollbar

import { getSafeArea, getScrollbarWidth } from 'viewportify';

const safeArea = getSafeArea();
// { top: 47, right: 0, bottom: 34, left: 0 }

const scrollbar = getScrollbarWidth();
// 17 (Windows), 0 (macOS with overlay scrollbars)

Breakpoints

import {
  getCurrentBreakpoint,
  matchesBreakpoint,
} from 'viewportify';

// Tailwind-style breakpoints: xs, sm, md, lg, xl, 2xl
console.log(getCurrentBreakpoint());  // 'lg'
console.log(matchesBreakpoint('md')); // true if >= 768px

Full Instance API

import { Viewportify } from 'viewportify';

const vp = new Viewportify({
  debounceTime: 100,           // Resize event debounce (ms)
  setCSSVariables: true,       // Auto-set CSS custom properties
  cssPrefix: '--vp',           // CSS variable prefix
  trackKeyboard: true,         // Track keyboard visibility
  iosVhFix: true,              // Apply iOS 100vh fix
  breakpoints: {               // Custom breakpoints
    xs: 0,
    sm: 640,
    md: 768,
    lg: 1024,
    xl: 1280,
    '2xl': 1536,
  },
  onChange: (info) => {        // Viewport change callback
    console.log('Viewport changed:', info);
  },
});

// Get info
console.log(vp.info);

// Subscribe to changes
const unsubscribe = vp.subscribe((info) => {
  console.log('New dimensions:', info.width, info.height);
});

// Breakpoint utilities
console.log(vp.getCurrentBreakpoint());  // 'lg'
console.log(vp.isAbove('md'));           // true
console.log(vp.isBelow('xl'));           // true
console.log(vp.isBetween('sm', 'lg'));   // true

// Media query tracking
const darkMode = vp.matchMedia('(prefers-color-scheme: dark)');
console.log(darkMode.matches);
darkMode.subscribe(({ matches }) => {
  console.log('Dark mode:', matches);
});

// Cleanup
unsubscribe();
vp.destroy();

βš›οΈ React Hooks

Basic Hooks

import {
  useViewport,
  useBreakpoint,
  useMediaQuery,
  useIsMobile,
  useOrientation,
  useKeyboard,
  useWindowSize,      // NEW: Simple width/height object
  useViewportSize,    // NEW: iOS-accurate width/height
  useElementSize,     // NEW: Measure any element by ref
} from 'viewportify';

function MyComponent() {
  // Full viewport info (re-renders on change)
  const viewport = useViewport();
  
  // Current breakpoint
  const breakpoint = useBreakpoint(); // 'sm' | 'md' | 'lg' | etc.
  
  // Media query
  const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
  
  // Simple device check
  const isMobile = useIsMobile();
  
  // Orientation
  const orientation = useOrientation(); // 'portrait' | 'landscape'
  
  // Keyboard state (mobile)
  const { visible: keyboardVisible, height: keyboardHeight } = useKeyboard();

  return (
    <div style={{ height: viewport.height }}>
      <p>Screen: {viewport.width} x {viewport.height}</p>
      <p>Breakpoint: {breakpoint}</p>
      <p>Mobile: {isMobile ? 'Yes' : 'No'}</p>
      <p>Orientation: {orientation}</p>
      {keyboardVisible && (
        <p>Keyboard height: {keyboardHeight}px</p>
      )}
    </div>
  );
}

useWindowSize - Window Dimensions Hook

Returns { width, height } that automatically updates on window resize.

import { useWindowSize } from 'viewportify';

function ResponsiveComponent() {
  const { width, height } = useWindowSize();
  
  return (
    <div>
      <p>Window: {width} x {height}</p>
      {width < 768 && <MobileNav />}
      {width >= 768 && <DesktopNav />}
    </div>
  );
}

useViewportSize - iOS-Accurate Dimensions Hook

Same as useWindowSize but returns iOS-accurate height (handles Safari toolbar issue).

import { useViewportSize } from 'viewportify';

function FullscreenHero() {
  const { width, height } = useViewportSize();
  
  // This height works correctly on iOS Safari!
  return (
    <div style={{ width, height, background: 'linear-gradient(...)' }}>
      <h1>Full Screen Hero</h1>
    </div>
  );
}

useElementSize - Measure Any Element by Ref

Pass a ref to any element and get its exact dimensions, updating automatically on resize.

import { useRef } from 'react';
import { useElementSize } from 'viewportify';

function MeasuredComponent() {
  const containerRef = useRef<HTMLDivElement>(null);
  const size = useElementSize(containerRef);
  
  return (
    <div ref={containerRef} style={{ width: '50%', padding: 20 }}>
      <p>This container is:</p>
      <p>{size.width}px wide Γ— {size.height}px tall</p>
      <p>Position: ({size.x}, {size.y})</p>
    </div>
  );
}

Returns:

{
  width: number;   // Element width
  height: number;  // Element height
  top: number;     // Distance from viewport top
  left: number;    // Distance from viewport left
  right: number;   // Distance from viewport right edge
  bottom: number;  // Distance from viewport bottom edge
  x: number;       // Same as left
  y: number;       // Same as top
}

πŸ”„ Window Resize Support (Non-React)

onWindowResize - Subscribe to Resize Events

import { onWindowResize } from 'viewportify';

// Subscribe to resize events
const unsubscribe = onWindowResize(({ width, height }) => {
  console.log(`Window resized: ${width} x ${height}`);
}, {
  debounce: 100,    // Optional: debounce in ms
  immediate: true,  // Optional: call immediately with current size
});

// Later: cleanup
unsubscribe();

observeElementSize - Watch Element Size Changes

import { observeElementSize } from 'viewportify';

const element = document.getElementById('my-element');

const observer = observeElementSize(element, (size) => {
  console.log('Element size:', size.width, size.height);
  console.log('Position:', size.x, size.y);
});

// Get current size anytime
console.log(observer.getSize());

// Later: cleanup
observer.disconnect();

getElementSize - One-time Element Measurement

import { getElementSize } from 'viewportify';

const element = document.getElementById('my-element');
const size = getElementSize(element);

console.log(size); // { width, height, top, left, right, bottom, x, y }

## 🎨 CSS Variables

When `setCSSVariables` is enabled (default), Viewportify sets these CSS custom properties on `:root`:

```css
:root {
  /* Basic dimensions */
  --vp-width: 1920px;
  --vp-height: 1080px;
  --vp-vh: 10.8px;         /* 1vh in pixels */
  --vp-vw: 19.2px;         /* 1vw in pixels */
  --vp-vmin: 10.8px;
  --vp-vmax: 19.2px;
  
  /* Modern viewport units (polyfilled) */
  --vp-svh: 900px;         /* Small viewport height */
  --vp-lvh: 1080px;        /* Large viewport height */
  --vp-dvh: 1020px;        /* Dynamic viewport height */
  --vp-svw: 1920px;
  --vp-lvw: 1920px;
  --vp-dvw: 1920px;
  
  /* Safe area insets */
  --vp-safe-top: 47px;
  --vp-safe-right: 0px;
  --vp-safe-bottom: 34px;
  --vp-safe-left: 0px;
  
  /* Extras */
  --vp-scrollbar: 17px;
  --vp-dpr: 2;
  --vp-keyboard-height: 0px;
  
  /* iOS 100vh fix */
  --vh: 10.8px;            /* 1vh accurate for iOS */
}

Using CSS Variables

/* Full-height section that works on iOS */
.hero {
  height: calc(var(--vh, 1vh) * 100);
  /* Or use the direct value */
  height: var(--vp-height);
}

/* Respect safe areas on notched devices */
.content {
  padding-top: var(--vp-safe-top);
  padding-bottom: var(--vp-safe-bottom);
}

/* Full viewport width minus scrollbar */
.full-width {
  width: calc(100vw - var(--vp-scrollbar));
}

/* Adjust for keyboard on mobile */
.chat-input {
  bottom: var(--vp-keyboard-height);
  transition: bottom 0.3s ease;
}

πŸ“Š ViewportInfo Object

The full viewport info object contains:

interface ViewportInfo {
  // Basic dimensions
  width: number;           // Viewport width
  height: number;          // Viewport height (iOS-accurate)
  vh: number;              // 1vh in pixels
  vw: number;              // 1vw in pixels
  vmin: number;            // min(vh, vw)
  vmax: number;            // max(vh, vw)
  
  // Modern viewport units
  svh: number;             // Small viewport height
  lvh: number;             // Large viewport height
  dvh: number;             // Dynamic viewport height
  svw: number;             // Small viewport width
  lvw: number;             // Large viewport width
  dvw: number;             // Dynamic viewport width
  
  // Screen info
  screenWidth: number;     // screen.width
  screenHeight: number;    // screen.height
  dpr: number;             // devicePixelRatio
  orientation: 'portrait' | 'landscape';
  
  // Device detection
  isTouch: boolean;
  isMobile: boolean;
  isTablet: boolean;
  isDesktop: boolean;
  isIOS: boolean;
  isAndroid: boolean;
  isSafari: boolean;
  isStandalone: boolean;   // PWA mode
  
  // Safe area
  safeArea: {
    top: number;
    right: number;
    bottom: number;
    left: number;
  };
  
  // Scrollbar
  scrollbarWidth: number;
  
  // Keyboard (mobile)
  isKeyboardVisible: boolean;
  keyboardHeight: number;
}

πŸ”§ Understanding iOS Viewport Issues

On iOS Safari, window.innerHeight and 100vh behave unexpectedly:

  1. Initial load: Includes the Safari toolbar in the measurement
  2. After scroll: Toolbar collapses, but 100vh doesn't update
  3. Result: Elements set to 100vh overflow the visible viewport

Viewportify solves this by using multiple measurement techniques:

  • CSS 100vh element injection for accurate measurement
  • visualViewport API for dynamic tracking
  • New CSS units (svh, lvh, dvh) when available
  • Automatic CSS variable updates

πŸ“± Platform Support

| Platform | Support | |----------|---------| | Chrome (Desktop) | βœ… Full | | Firefox (Desktop) | βœ… Full | | Safari (Desktop) | βœ… Full | | Edge (Desktop) | βœ… Full | | Chrome (Android) | βœ… Full | | Safari (iOS) | βœ… Full (with fixes) | | Firefox (Android) | βœ… Full | | Samsung Internet | βœ… Full | | PWA (All) | βœ… Full | | SSR (Node.js) | βœ… Safe (returns zeros) |

🀝 Comparison with Alternatives

| Feature | Viewportify | ios-inner-height | viewport-dimensions | |---------|-------------|------------------|---------------------| | iOS Safari fix | βœ… | βœ… | ❌ | | svh/lvh/dvh | βœ… | ❌ | ❌ | | CSS variables | βœ… | ❌ | ❌ | | React hooks | βœ… | ❌ | ❌ | | useWindowSize | βœ… | ❌ | ❌ | | useElementSize (ref) | βœ… | ❌ | ❌ | | Resize subscription | βœ… | ❌ | βœ… | | Keyboard detection | βœ… | ❌ | ❌ | | Safe area insets | βœ… | ❌ | ❌ | | Breakpoints | βœ… | ❌ | ❌ | | TypeScript | βœ… | ❌ | ❌ | | Tree-shakable | βœ… | N/A | ❌ | | Last updated | 2025 | 2018 | 2014 | | Bundle size | ~5kb | ~1kb | ~2kb |

πŸ“ License

MIT Β© Viewportify Contributors


Made with ❀️ for developers tired of viewport inconsistencies

⭐ Star us on GitHub if this saved you time!