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

@jielga/react-popup-window

v0.1.0

Published

React hook to open part of your UI in a separate browser popup window — live portal rendering, style syncing and cross-window messaging.

Readme

@jielga/react-popup-window

CI npm license

React hook for rendering part of a component tree in a separate browser window. Content is rendered through a portal into the popup document, so it remains part of the calling tree: state, context, and event handlers work across windows without bridging.

Documentation and live examples

Features

  • Portal-based rendering — popup content keeps access to all ancestor context (state managers, data fetching, theming, routing)
  • Stylesheet synchronization — <style> and <link> elements, root class/data-* attributes, and adoptedStyleSheets are mirrored into the popup and kept current while it is open
  • Lifecycle management — detects the user closing the window, closes the popup on owner unmount and opener unload, reports blocked popups
  • No dependencies beyond react and react-dom
  • TypeScript, ESM and CJS builds, SSR-safe

Installation

npm install @jielga/react-popup-window

Requires React 19.2 or later.

Usage

import { usePopupWindow } from '@jielga/react-popup-window'

function Dashboard() {
  const { open, close, isOpen, Popup } = usePopupWindow({
    title: 'Detached panel',
    features: { width: 640, height: 480 },
  })

  return (
    <>
      <button onClick={open}>Open in new window</button>
      <Popup>
        <MyPanel />
      </Popup>
    </>
  )
}

Popup renders its children into the popup window while open and nothing otherwise. It has a stable identity and can be destructured from the hook result.

Detaching a section

To hide a section in the main window while it is popped out, render it in one of two places depending on isOpen:

const { open, close, focus, isOpen, Popup } = usePopupWindow({ title: 'People' })

const table = <DataTable />

return (
  <>
    {isOpen ? (
      <div>
        <button onClick={focus}>Focus window</button>
        <button onClick={close}>Bring back</button>
      </div>
    ) : (
      <>
        <button onClick={open}>Open in new window</button>
        {table}
      </>
    )}
    <Popup>{table}</Popup>
  </>
)

isOpen also updates when the user closes the window directly, so the inline branch is restored in every case.

How it works

The hook opens a same-origin about:blank window and creates a container element in its body. The Popup component renders its children with createPortal into that container. A portal changes where DOM output is placed, not where the components sit in the tree, so popup content participates in the calling tree's state, context, and event system. React supports cross-document portals: it attaches its event delegation to the portal container when the container belongs to another document.

Physically moving DOM nodes into another document is not a viable alternative: React delegates events on the root container in the main document, and a node moved elsewhere stops receiving synthetic events.

The popup document runs no JavaScript of its own. All rendering and event handling execute in the opener window.

API

usePopupWindow(options?)

interface UsePopupWindowOptions {
  /** Popup document title. Defaults to the opener document's title. */
  title?: string
  /** window.open target name. The same name reuses the window. Default: '_blank'. */
  name?: string
  /** window.open features, merged over { popup: true, width: 640, height: 480 }. */
  features?: PopupWindowFeatures
  /** Center the popup over the opener when no left/top feature is given. Default: true. */
  center?: boolean
  /** Mirror and synchronize stylesheets into the popup. Default: true. */
  copyStyles?: boolean
  /** Called after the popup window is opened and prepared. */
  onOpen?: (popupWindow: Window) => void
  /** Called when the popup closes: close(), user close, or opener unload. */
  onClose?: () => void
  /** Called when window.open returns null (popup blocked). */
  onBlocked?: () => void
}

Returns:

| Member | Type | Description | | ------------- | -------------------------- | ------------------------------------------------------------------------------------------------- | | Popup | FC<{ children? }> | Portal component. Renders children into the popup while open. | | open | () => Window \| null | Opens the popup, or focuses it if already open. Returns null when blocked. Requires a user gesture. | | close | () => void | Closes the popup. | | toggle | () => void | Opens if closed, closes if open. | | focus | () => void | Focuses the popup window. | | isOpen | boolean | Whether the popup is open. | | isBlocked | boolean | Whether the last open() call was blocked. | | popupWindow | Window \| null | The popup Window while open. |

copyStyles(source, target, watch?)

The style synchronization used by the hook, exported for windows managed outside of it. Copies stylesheets from source to target and, when watch is true (default), observes the source document for changes. Returns a function that stops observing.

Style synchronization

While the popup is open, the following are mirrored from the opener document and kept current:

  • <style> and <link rel="stylesheet"> elements. <style> contents are serialized from the CSSOM, so rules injected with insertRule are included.
  • Additions, removals, and text edits of style nodes in <head>. This covers Vite HMR, lazily loaded chunk CSS, and CSS-in-JS libraries.
  • class, style, and data-* attributes on <html> and <body>. Theme systems keyed on root attributes propagate to the popup.
  • document.adoptedStyleSheets.

Set copyStyles: false to disable.

Communication

Popup content rendered through Popup is part of the calling component tree and executes in the opener's JavaScript realm. Props, state, and context are the communication mechanism; no message channel is required or provided.

postMessage remains relevant only for scripts hosted in the popup document itself (for example, an injected non-React widget). Such scripts run in the popup's realm and can post to window.opener; the exposed popupWindow handle can be used from the opener side. Note that calling opener.postMessage from a portal event handler posts from the opener's own realm — the browser reports the main window, not the popup, as event.source.

Portal-based UI libraries

Component libraries typically mount overlays — menus, popovers, modals, tooltips — into document.body, which is the main window's body even for components rendered inside the popup. Provide a portal target inside the popup document instead:

  • Mantine: portal defaults can be set through the theme. See SameWindowPortals for a wrapper that resolves its own ownerDocument and supplies that document's body as the default Portal target.
  • Radix, MUI, and similar: use the per-component portal container prop with popupWindow.document.body.

Limitations

  • Browsers do not allow hiding the address bar entirely. popup: true (the default) requests the minimal window chrome the platform provides.
  • open() must be called from a user gesture; otherwise the browser's popup blocker intervenes and open() returns null.
  • Popup content unmounts and remounts when it moves between windows. State that should survive detaching belongs in the component that owns the hook, or in an external store.
  • The popup closes when the owning component unmounts and when the opener window unloads. The popup document cannot outlive the opener.

Agent skills

The package ships Agent Skills for AI coding agents, managed with @tanstack/intent:

npx @tanstack/intent@latest list
npx @tanstack/intent@latest load @jielga/react-popup-window#getting-started

| Skill | Contents | | ----------------- | -------------------------------------------------------------------- | | getting-started | Hook API, options, lifecycle, common mistakes | | popup-content | Style synchronization, bounded-height layouts, portal-based overlays |

Development

| Path | Contents | | ----------- | --------------------------------------------------------------- | | src/ | Library source. Vite library mode; ESM, CJS, and declarations. | | docs/ | Documentation site with live examples, deployed to GitHub Pages. | | e2e/ | Playwright tests that exercise real popup windows in Chromium. | | skills/ | Agent skills shipped with the package. |

npm install
npm run dev             # documentation site with the library aliased to source
npm test                # unit tests (Vitest, jsdom)
npm run e2e             # end-to-end tests (Playwright)
npm run build           # build the library into dist/
npm run skills:validate # validate agent skills
npm run changeset       # describe a change for the changelog and the next release

Releases are published to npm by Changesets when the chore: version packages pull request is merged — see RELEASING.md.

License

MIT