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

@gukii/mobile-safari-tab-group-scroll

v0.1.1

Published

React hook for detecting iOS Safari tab-group viewport loss and applying a scroll correction.

Readme

@gukii/mobile-safari-tab-group-scroll

React hook for detecting the extra iOS Safari tab-group row and applying a delayed scroll correction.

This is intended for full-screen mobile web apps where iPhone/iPad Safari tab groups can reduce the usable viewport while the app still lays out like the full viewport is available.

Install

From npm after publishing:

pnpm add @gukii/mobile-safari-tab-group-scroll

From a public GitHub repo before publishing:

pnpm add github:gukii/mobile-safari-tab-group-scroll

The package commits dist/, so installing from GitHub does not need build-script allowlisting.

From a local checkout:

pnpm add /absolute/path/to/mobile-safari-tab-group-scroll

In a pnpm workspace:

{
  "dependencies": {
    "@gukii/mobile-safari-tab-group-scroll": "workspace:*"
  }
}

Install it in another app from GitHub:

pnpm add github:gukii/mobile-safari-tab-group-scroll

Publishing to npm later is optional:

pnpm publish --access public

Basic Usage

Import the hook once near the root of your React app.

import { useMobileSafariTabGroupScroll } from "@gukii/mobile-safari-tab-group-scroll";
import "@gukii/mobile-safari-tab-group-scroll/styles.css";

export function App() {
  const safariTabGroup = useMobileSafariTabGroupScroll();

  return (
    <main>
      {safariTabGroup.isLikelyTabGroupChromeVisible ? (
        <div>
          Safari tab group detected: {safariTabGroup.offsetPx}px
        </div>
      ) : null}
      <YourApp />
    </main>
  );
}

With A Settings Toggle

import { useState } from "react";
import { useMobileSafariTabGroupScroll } from "@gukii/mobile-safari-tab-group-scroll";
import "@gukii/mobile-safari-tab-group-scroll/styles.css";

export function App() {
  const [enabled, setEnabled] = useState(true);
  const safariTabGroup = useMobileSafariTabGroupScroll({ enabled });

  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={enabled}
          onChange={(event) => setEnabled(event.target.checked)}
        />
        Safari tab-group correction
      </label>

      {safariTabGroup.isLikelyTabGroupChromeVisible ? (
        <div>Correcting by {safariTabGroup.scrollPx}px</div>
      ) : null}
    </>
  );
}

SSR / Client-Only Wrapper

For SSR frameworks, use MobileSafariTabGroupScrollCorrection. It is a client component and waits until after client mount before calling the hook.

import { MobileSafariTabGroupScrollCorrection } from "@gukii/mobile-safari-tab-group-scroll";
import "@gukii/mobile-safari-tab-group-scroll/styles.css";

export function SafariViewportCorrection() {
  return (
    <MobileSafariTabGroupScrollCorrection>
      {(state) =>
        state.isLikelyTabGroupChromeVisible ? (
          <div role="status">
            Safari tab group corrected by {state.scrollPx}px
          </div>
        ) : null
      }
    </MobileSafariTabGroupScrollCorrection>
  );
}

Render it once near the root of your app. If you do not need UI, omit children:

<MobileSafariTabGroupScrollCorrection />

Next.js App Router

Create a client component:

// app/SafariViewportCorrection.tsx
"use client";

import { MobileSafariTabGroupScrollCorrection } from "@gukii/mobile-safari-tab-group-scroll";

export function SafariViewportCorrection() {
  return <MobileSafariTabGroupScrollCorrection />;
}

Import the CSS and render the client component from your root layout:

// app/layout.tsx
import "@gukii/mobile-safari-tab-group-scroll/styles.css";
import { SafariViewportCorrection } from "./SafariViewportCorrection";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SafariViewportCorrection />
        {children}
      </body>
    </html>
  );
}

TanStack Start

Render the correction component in your root route/component:

import { createFileRoute, Outlet } from "@tanstack/react-router";
import { MobileSafariTabGroupScrollCorrection } from "@gukii/mobile-safari-tab-group-scroll";
import "@gukii/mobile-safari-tab-group-scroll/styles.css";

export const Route = createFileRoute("/__root")({
  component: RootComponent,
});

function RootComponent() {
  return (
    <>
      <MobileSafariTabGroupScrollCorrection />
      <Outlet />
    </>
  );
}

CSS Integration

The package CSS defines:

:root {
  --mobile-safari-tab-group-offset: 0px;
  --@gukii/mobile-safari-tab-group-scroll: var(--mobile-safari-tab-group-offset);
}

When a tab group is detected, the hook adds this class to <html>:

mobile-safari-tab-group-detected

The included CSS gives the page enough scrollable height for the correction:

import "@gukii/mobile-safari-tab-group-scroll/styles.css";

If your app has stronger mobile layout rules such as overflow: hidden on html, body, or #root, import this CSS after your app CSS or copy the rule into your app stylesheet.

Options

type MobileSafariTabGroupScrollOptions = {
  enabled?: boolean;
  applyScrollCorrection?: boolean;
  logDetection?: boolean;
  storagePrefix?: string;
  tabGroupMinPx?: number;
  tabGroupMaxPx?: number;
  defaultTabGroupOffsetPx?: number;
  scrollCorrectionMultiplier?: number;
  expectedViewportRatios?: {
    portrait?: number;
    landscape?: number;
  };
  knownAppleScreenSizes?: Iterable<string>;
  offsetCssVariable?: string;
  scrollCssVariable?: string;
  detectedClassName?: string;
  correctionDelaysMs?: number[];
};

Defaults match the behavior developed for this app:

useMobileSafariTabGroupScroll({
  scrollCorrectionMultiplier: 3,
  correctionDelaysMs: [120, 300, 500, 800, 1200],
});

applyScrollCorrection performs one controlled correction sequence per detected offset. It does not continuously force the scroll position. The correctionDelaysMs values are delayed retries inside that single sequence so Safari can finish its first layout pass before the final correction runs.

Return Value

type MobileSafariTabGroupScrollState = {
  isLikelyMobileSafari: boolean;
  isLikelyTabGroupChromeVisible: boolean;
  offsetPx: number;
  scrollPx: number;
};

Exports

import {
  MobileSafariTabGroupScrollCorrection,
  useMobileSafariTabGroupScroll,
  useMobileSafariTabGroupOffset,
} from "@gukii/mobile-safari-tab-group-scroll";

useMobileSafariTabGroupOffset is a compatibility alias for useMobileSafariTabGroupScroll.

Notes

  • This is a pragmatic heuristic, not an official Safari API.
  • The hook combines several signals:
    • iOS Safari user-agent detection
    • known Apple screen point sizes
    • visualViewport.height
    • innerHeight - visualViewport.height
    • a stored best-seen viewport baseline by device and orientation
    • expected Safari viewport ratios
  • It avoids applying corrections while the keyboard is likely open.
  • If Safari changes its viewport reporting, tune expectedViewportRatios, tabGroupMinPx, tabGroupMaxPx, or scrollCorrectionMultiplier.