The web browser continues to erode the boundary between web applications and native desktop software. With the release of Firefox 151, Mozilla has officially shipped support for the Document Picture-in-Picture (DPIP) API. This specification, developed under the Web Incubator Community Group (WICG), represents a major expansion beyond traditional browser window capabilities.
For years, web developers and users have been familiar with standard Picture-in-Picture functionality. However, that legacy implementation is restricted entirely to HTML video elements, allowing users to pop a video stream out into a floating, resizable window that remains visible while switching tabs or navigating other desktop applications. The Document Picture-in-Picture API fundamentally changes this dynamic: rather than limiting the always-on-top window to media playback, it allows developers to place any arbitrary HTML content, styled with CSS and driven by JavaScript, into an independent floating viewport.
This capability effectively enables developers to construct fully functional, floating web widgets. Applications can now feature persistent stock tickers, interactive live chat modules, music playlists, micro-note apps, task lists, or lightweight spreadsheet views that remain pinned above other open windows regardless of operating system navigation.
While the conceptual model of creating a Document Picture-in-Picture window and populating it with DOM nodes is straightforward, practical implementation requires careful handling of browser support, document cloning, layout performance, and contextual CSS styles.
The JavaScript of It All
Implementing the Document Picture-in-Picture API begins with feature detection. Because DPIP is currently designed as a desktop-only browser capability, applications must verify API availability before offering floating controls to the user.
In an ideal CSS architecture, developers might prefer to handle feature detection declaratively using media feature queries, suppressing or displaying layout triggers based on browser support. However, querying support for media features like (display-mode: picture-in-picture) via CSS conditional rules—such as @supports at-rule(@media; display-mode: picture-in-picture)—is not widely viable across standard browsers. The at-rule() function has seen limited engine adoption, primarily appearing in experimental or recent builds like Chrome, with early support noted in experimental channels such as Safari Technology Preview 251 and recent Firefox development releases.
Consequently, feature detection for the Document Picture-in-Picture API must be executed programmatically through JavaScript by inspecting the window object:
if (!("documentPictureInPicture" in window))
/* DPIP not supported (remove button) */
document.querySelector("button").remove();
else
/* DPIP supported (listen for button click) */
document.querySelector("button").addEventListener("click", async () =>
/* ... */
);
When initiating a DPIP window, developers call the requestWindow() method on the window.documentPictureInPicture interface. Because requestWindow() returns a Promise, creation of the window is asynchronous, allowing the main script thread to configure window contents while the sub-window context initializes.
The requestWindow() method accepts an optional configuration object that dictates the initial dimensions and behaviors of the window:
/* Create the DPIP window */
const DPIP = await window.documentPictureInPicture.requestWindow(
width: 600,
height: 400,
preferInitialWindowPlacement: true
);
The width and height parameters define the initial pixel dimensions of the pop-out window. These options must be provided together; specifying one without the other is invalid, and omitting both defers dimension choices entirely to the user agent.
The boolean option preferInitialWindowPlacement, when set to true, instructs the browser to open the window at its default position and size rather than re-using the user’s previously saved window placement and dimensions. Another configurable parameter is disallowReturnToOpener. When set to true, this option hides the browser’s built-in "Back to tab" control inside the window frame, which typically serves to close the floating viewport and refocus the originating web tab.
Managing window lifecycles is another practical consideration. When a user interacts with a toggle control to open a floating panel, an existing DPIP window associated with the document might already be open. Standard browser behavior replaces active DPIP instances automatically, but developers can explicit control behavior by querying window.documentPictureInPicture.window:
document.querySelector("button").addEventListener("click", async () =>
/* If the DPIP window is open, close it */
if (window.documentPictureInPicture.window)
window.documentPictureInPicture.window.close();
);
However, because opening or interacting with a DPIP window automatically shifts user focus to the new context, programmatically toggling the window closed from the main page control can require multiple user clicks. In practice, many implementation patterns allow repeated clicks on the trigger element to re-instantiate or reset the floating window to its default dimensions.
Once the window reference is secured, content from the main document must be moved or duplicated into the new target window context (DPIP.document). To display a target component, such as an interactive stock ticker element, standard DOM manipulation methods like cloneNode() are employed:
/* Select the component */
const stock = document.querySelector("#stock");
/* Clone the component and append it to the DPIP <body> */
DPIP.document.body.append(stock.cloneNode(true));
While copying a single HTML container transfers the markup tree, it does not automatically transfer the associated visual styling. Without attached stylesheets, the extracted HTML component in the floating window will render unstyled.
To bridge this gap efficiently, developers must collect and transfer existing style rules—including both inline <style> blocks and linked stylesheets (<link rel="stylesheet">)—into the floating document’s <head>. Appending nodes individually to a newly constructed document tree can trigger repeated, costly browser layout reflows. To optimize performance, stylesheets should be collected into a single DocumentFragment before performing a single append operation to the target window:
/* Select all <style>s and <link rel=stylesheet>s */
const styles = document.querySelectorAll("style, [rel=stylesheet]");
/* Create a document fragment */
const documentFragment = document.createDocumentFragment();
/* Clone the styles and append them to the DPIP <head> */
styles.forEach((element) =>
documentFragment.append(element.cloneNode(true))
);
/* Append the document fragment to the DPIP <head> */
DPIP.document.head.append(documentFragment);
Combining these procedural steps results in a robust script for instantiating functional, fully styled floating windows:
if (!("documentPictureInPicture" in window))
/* DPIP not supported (remove button) */
document.querySelector("button").remove();
else
/* DPIP supported (listen for button click) */
document.querySelector("button").addEventListener("click", async () =>
/* Create the DPIP window */
const DPIP = await window.documentPictureInPicture.requestWindow(
width: 600,
height: 400,
preferInitialWindowPlacement: true
);
/* Select the component */
const stock = document.querySelector("#stock");
/* Clone the component and append it to the DPIP <body> */
DPIP.document.body.append(stock.cloneNode(true));
/* Select all <style>s and <link rel=stylesheet>s */
const styles = document.querySelectorAll("style, [rel=stylesheet]");
/* Create a document fragment */
const documentFragment = document.createDocumentFragment();
/* Clone the styles and append them to the DPIP <head> */
styles.forEach((element) =>
documentFragment.append(element.cloneNode(true))
);
/* Append the document fragment to the DPIP <head> */
DPIP.document.head.append(documentFragment);
);
When building these integrations, developers must note that Document Picture-in-Picture operations are restricted from executing within nested browsing contexts, such as cross-origin or standard <iframe> environments, requiring testing to take place in top-level browsing contexts.
Handling the CSS
Moving DOM elements from a primary page context into a dedicated sub-window introduces distinct structural challenges for CSS styling. In a standard web document, component styles frequently depend on contextual parent rules, specific class cascades, or structural wrapper elements. Extracting an element and placing it directly inside the <body> of a Document Picture-in-Picture window breaks those parent-child selector relationships.
To ensure elements adapt visually when displayed within a floating window, developers can leverage the display-mode media feature query. By targeting (display-mode: picture-in-picture), CSS declarations apply exclusively when the document is rendered inside a Picture-in-Picture viewport:
#stock
width: fit-content;
border-radius: 0.7rem;
@media (display-mode: picture-in-picture)
width: 100%;
height: 100%;
border-top-left-radius: 0;
border-top-right-radius: 0;
This media query allows the element to maintain fixed dimensions or custom borders when embedded in the primary layout, while fluidly expanding to fill 100% of the width and height of the dedicated floating viewport.
It is critical for front-end engineers to distinguish between the @media (display-mode: picture-in-picture) query and the CSS :picture-in-picture pseudo-class. The :picture-in-picture pseudo-class is explicitly designated for matching standard <video> elements currently operating under the legacy Picture-in-Picture API. It does not match container elements or general HTML structures detached via the Document Picture-in-Picture API.
Wrapping Up
The Document Picture-in-Picture API also exposes event hooks on the documentPictureInPicture interface to allow application logic to respond to window creation state changes. Specifically, the enter event fires immediately when a floating document window is successfully instantiated and presented to the user:
documentPictureInPicture.addEventListener("enter", (event) =>
/* DPIP window opened */
);
This lifecycle event is distinct from the legacy enterpictureinpicture event used by standard HTML video elements.
With Firefox 151 joining Google Chrome in supporting the Document Picture-in-Picture API, desktop web applications have a standardized, lightweight path toward rendering true auxiliary UI components. As engine adoption broadens, the ability to split web applications across multi-window contexts brings web software significantly closer to the desktop capabilities historically reserved for native applications.