Document Picture-in-Picture: always-on-top window
Published
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
Section titled “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
Section titled “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
Section titled “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:
// 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
Section titled “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):
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
Section titled “Practical checklist”requestWindow()requires transient activation — call it only from inside a user-gesture handler such as a click listener, per the MDNrequestWindow()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 owndocument.headfor 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
enterevent 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 aMediaSessionaction handler with a type ofenterpictureinpictureinstead.