The launch of Firefox 151 marks a significant milestone for web interface design, as Mozilla has officially shipped support for the Document Picture-in-Picture (DPIP) API. While developers and users have long been familiar with standard Picture-in-Picture functionality, which restricts floating windows strictly to video elements, the Document Picture-in-Picture API introduces a far broader capability: the ability to pop arbitrary web content—including HTML, CSS, and JavaScript—out of the browser tab and into an always-on-top window.
This development bridges a persistent functional gap between native desktop applications and web applications. By allowing full DOM nodes to exist inside floating windows, developers can construct persistent desktop web widgets. Practical implementations include floating stock tickers, live customer support chat windows, persistent media playlists, interactive to-do lists, scratchpads, or real-time web spreadsheets that remain visible regardless of whether the originating browser tab or operating system window remains focused.
Creating a Document Picture-in-Picture window requires initializing a dedicated browsing context, then cloning or transferring HTML markup, CSS styling, and JavaScript logic directly into the floating frame. While the foundational mechanics of the API are straightforward, implementing component pop-outs in practical scenarios involves navigating browser feature detection, managing element lifecycle states, optimizing DOM tree performance, and accounting for contextual CSS changes.
The JavaScript of It All
Implementing the Document Picture-in-Picture API begins with verifying browser compatibility. Because the API is currently confined to desktop environments and supported in Chromium-based browsers alongside Firefox 151, cross-browser applications must detect feature availability before offering the capability to users. Notably, Apple’s Safari does not yet support the Document Picture-in-Picture API in its stable release, necessitating explicit runtime feature checks.
From a purely CSS-oriented perspective, detecting support via feature queries such as @supports would be ideal. Ideally, developers could query media feature support directly within stylesheet rules using syntax like the following:
@supports at-rule(@media; display-mode: picture-in-picture)
/* DPIP supported */
However, querying media features via @supports using the at-rule() function is currently impractical across the browser landscape. Google Chrome supports at-rule(), but plans to evaluate preludes like (display-mode: picture-in-picture) within feature queries have been set aside. Emerging browser builds indicate movement in this area—release notes for Safari Technology Preview 251 mention preliminary support for at-rule detection within @supports, and pre-release documentation for Firefox 155 notes similar exploratory capabilities—but full cross-browser CSS-based detection remains unavailable.
Consequently, developers must rely on JavaScript feature detection. If the documentPictureInPicture property is absent from the global window object, the associated interface controls can be hidden or removed from the main DOM. If supported, event listeners can be attached to trigger window creation:
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 () =>
/* ... */
);
This runtime verification accounts for both unsupported browsers and mobile environments, where the Document Picture-in-Picture API is intentionally omitted.
When managing the lifecycle of a Document Picture-in-Picture window, opening a new floating frame automatically supersedes any previously opened DPIP window associated with the document. However, developers must consider user interaction patterns when a trigger button is clicked multiple times. One option is to configure the trigger as a toggle switch that checks for an active DPIP window instance and closes it if present:
document.querySelector("button").addEventListener("click", async () =>
/* If the DPIP window is open, close it */
if (window.documentPictureInPicture.window)
window.documentPictureInPicture.window.close();
);
Because focus automatically transfers to the newly created DPIP window upon launch, toggling the window closed from the parent context can introduce minor interaction friction, sometimes requiring multiple focus interactions. While developers could theoretically clone the toggle control into the floating window itself, browser-native picture-in-picture windows already include standard window controls, including a built-in "Close" button. Alternatively, allowing subsequent user clicks on the primary trigger to simply re-initialize the DPIP window serves to reset its position and dimensions to default values.
Window initialization is requested asynchronously using the requestWindow() method exposed on window.documentPictureInPicture. The method accepts a configuration object that controls initial dimensions and window behaviors:
/* Create the DPIP window */
const DPIP = await window.documentPictureInPicture.requestWindow(
width: 600,
height: 400,
preferInitialWindowPlacement: true
);
The width and height properties define the dimensions of the newly spawned desktop window. Both values must be declared together; if omitted entirely, the browser determines default structural dimensions. Setting preferInitialWindowPlacement to true instructs the browser not to persist the user’s previous window positioning or sizing adjustments across sessions. Another available parameter, disallowReturnToOpener, can be set to true to suppress the browser’s default "Back to tab" UI control, which normally allows users to close the floating frame while simultaneously shifting active browser focus back to the originating tab.
Because requestWindow() returns a promise, execution pauses until the browser constructs the floating frame, at which point DOM nodes can be populated into the new document context. Inserting individual HTML components can be performed by selecting the target node from the host document, cloning it, and appending it to the body of the picture-in-picture window:
/* 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));
Populating associated stylesheets and external resources requires a broader approach. Rather than copying nodes individually—which triggers multiple costly layout recalculations (reflows) in the new window—developers can collect all <style> elements and <link rel="stylesheet"> tags from the main document, append them into a DocumentFragment, and batch-insert them into the floating window’s <head>.
/* 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);
By leveraging an off-screen DocumentFragment, the target document tree processes the complete style set in a single operation, minimizing rendering overhead and preventing visual flicker during window initialization.
The combined script demonstrates a foundational implementation that handles capability detection, window instantiation, DOM component cloning, and style inheritance:
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);
);
Developers testing this implementation should note that picture-in-picture functionality is restricted within nested browsing contexts, such as <iframe> containers common to online code playgrounds like CodePen. Testing requires opening isolated frames or direct page contexts.
Handling the CSS
Extracting an HTML component from its original document context and placing it within an isolated window can break CSS assumptions. Class selectors that rely on specific parent ancestry or rigid structural hierarchies may fail to match once the node resides directly inside the secondary window’s <body>. Consequently, CSS rules intended for portable components should remain loosely coupled from broader document wrappers.
To apply distinct visual presentation specifically when a component is displayed inside a floating frame, developers can utilize the standard display-mode media feature. By querying @media (display-mode: picture-in-picture), styles can adapt fluidly to the floating viewport constraints without impacting the layout of the main document:
#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;
In this context, the targeted element adjusts its dimensions to consume the full width and height of the picture-in-picture window while overriding corner radius values to align seamlessly with the native window borders.
It is critical 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 applies strictly to traditional <video> elements instantiated through the legacy Video Picture-in-Picture API. It does not match DOM elements styled within Document Picture-in-Picture windows.
Wrapping Up
The Document Picture-in-Picture API also provides explicit lifecycle event listeners. Specifically, the enter event fires on the documentPictureInPicture object at the moment a floating window is created:
documentPictureInPicture.addEventListener("enter", (event) =>
/* DPIP window opened */
);
This event allows developers to register state changes or trigger background syncing when the floating context becomes active. Similar to media queries, developers should take care not to confuse this event with enterpictureinpicture, which is dedicated exclusively to video-based picture-in-picture instances.
With Firefox 151 joining Chrome in offering support for the specification, the Document Picture-in-Picture API provides web developers with a streamlined, standardized toolset for delivering persistent, desktop-level web interfaces without requiring specialized wrapper frameworks.