# Document Picture-in-Picture: always-on-top window

> How the Document Picture-in-Picture API opens an always-on-top HTML window, its requestWindow() options, feature detection, and Chrome/Firefox browser support.

**In one line:** per MDN, "the Document Picture-in-Picture API makes it possible to open
an always-on-top window that can be populated with arbitrary HTML content," extending the
earlier Picture-in-Picture API that only supports a single `<video>` element.

## What it is

MDN describes the Document Picture-in-Picture window as similar to a same-origin window
opened with `Window.open()`, with a few differences: it "floats on top of other windows,"
it "never outlives the opening window," it "cannot be navigated," and "the Picture-in-Picture
window position cannot be set by the website." MDN also notes the window "is limited to one
per browser tab at a time." MDN's listed use cases include an always-on-top custom video
player, a video-conferencing control surface, and always-visible productivity tools such as
timers or to-do lists.

## Where it is supported

Per MDN's browser-compat-data, the `DocumentPictureInPicture` interface and its
`requestWindow()` method are supported in Chrome from version 116 (Edge mirrors Chrome) and
in Firefox from version 151. The same data records the feature as unsupported in Safari, and
unsupported on Chrome for Android and Firefox for Android (`version_added: false` in each
case).

## How to use it

Per MDN, the entry point is `Window.documentPictureInPicture`, and the window is opened by
calling `DocumentPictureInPicture.requestWindow()`, which "returns a Promise that fulfills
with the window's own Window object." Per the `requestWindow()` reference, the call "requires
transient activation," so it must run inside a user-gesture handler such as a click listener,
and it accepts an options object with `width`, `height`, `disallowReturnToOpener`, and
`preferInitialWindowPlacement`:

```js
// Create the content that will move into the Picture-in-Picture window, and the
// container it lives in while docked in the main document.
const mainContainer = document.createElement("div");
const playerContainer = document.createElement("div");
playerContainer.textContent = "Now playing…";
mainContainer.append(playerContainer);
document.body.append(mainContainer);

const button = document.createElement("button");
button.textContent = "Pop out player";
document.body.append(button);

button.addEventListener("click", async () => {
  if (!("documentPictureInPicture" in window)) {
    // No Document Picture-in-Picture support — leave the player docked in the
    // main document instead of throwing.
    return;
  }

  const pipWindow = await documentPictureInPicture.requestWindow({
    width: 320,
    height: 180,
  });

  // Copy the app's stylesheets so moved content keeps its styling.
  [...document.styleSheets].forEach((sheet) => {
    try {
      const cssRules = [...sheet.cssRules].map((rule) => rule.cssText).join("");
      const style = document.createElement("style");
      style.textContent = cssRules;
      pipWindow.document.head.appendChild(style);
    } catch (e) {
      // Skip any stylesheet we can't read here.
    }
  });

  pipWindow.document.body.append(playerContainer);

  pipWindow.addEventListener("pagehide", () => {
    // Move the content back into the main window when the PiP window closes.
    mainContainer.append(playerContainer);
  });
});
```

## How to detect it at runtime

Feature-detect `documentPictureInPicture` on `window` before calling `requestWindow()`, and
fall back to a no-op when it is absent — the click handler above already guards the call
this way, but the check can be reused standalone wherever the app needs to know up front
whether pop-out is available (for example, to hide the "Pop out player" button entirely):

```js
function supportsDocumentPictureInPicture() {
  return "documentPictureInPicture" in window;
}

if (!supportsDocumentPictureInPicture()) {
  // No Document Picture-in-Picture support — hide any UI that offers pop-out and
  // keep everything in the main document instead.
  document.querySelectorAll("[data-requires-document-pip]").forEach((el) => {
    el.hidden = true;
  });
}
```

## Practical checklist

- `requestWindow()` requires transient activation — call it only from inside a user-gesture
  handler such as a click listener, per the MDN `requestWindow()` reference.
- The Picture-in-Picture window "never outlives the opening window" and "cannot be
  navigated," per MDN — do not rely on it as an independent, long-lived window.
- Stylesheets are not copied automatically; append `<style>` or `<link>` elements to the
  Picture-in-Picture window's own `document.head` for the moved content to render correctly.
- MDN's browser-compat-data records this as unsupported on Chrome for Android and Firefox
  for Android as well as on Safari, so feature-detect on every platform rather than assuming
  desktop-only gaps.
- Per MDN, the `enter` event only fires when your own code opens the window
  programmatically; when the browser itself moves content into the window (for example
  because the tab was switched), that event does not fire — use a `MediaSession`
  action handler with a type of `enterpictureinpicture` instead.

## Where to go next

- [Screen Wake Lock API](/reference/capabilities/wake-lock/)
- [WebRTC](/reference/capabilities/webrtc/)