With the public release of Firefox 151, Mozilla has officially shipped support for the Document Picture-in-Picture API, bringing a versatile user interface capability to the browser’s desktop platform. The addition marks a major step forward for web application developers seeking to offer persistent, multi-window experiences across operating system workspaces.
While traditional Picture-in-Picture functionality has been available in modern web browsers for years, its capabilities have historically been restricted to media content. The standard Picture-in-Picture API isolates HTML <video> elements and pops them into a floating, resizable window that stays pinned above other applications, surviving tab switches and desktop window toggles.
The Document Picture-in-Picture (DPIP) API fundamentally expands this paradigm. Rather than limiting the floating window to video streams, the DPIP specification allows developers to place arbitrary web content into an always-on-top window—effectively transforming standard DOM elements into standalone web widgets.
By allowing complete web documents to exist in pinned desktop windows, the API unlocks broad architectural possibilities for web applications. Developers can construct floating stock tickers, persistent live chat widgets, media controllers, interactive playlists, task lists, floating note pads, and real-time financial spreadsheets. Anything constructed using standard web technologies can be detached from the primary browser viewport and maintained on-screen continuously while users navigate elsewhere.
Despite its versatility, implementing the Document Picture-in-Picture API requires navigating specific architectural constraints, cross-browser compatibility nuances, dynamic DOM manipulation strategies, and tailored CSS design rules.
The JavaScript of it all
Deploying the Document Picture-in-Picture API in production applications requires careful feature detection and window lifecycle management. Because DPIP is strictly a desktop-oriented web API, web applications must gracefully account for unsupported environments, such as mobile browsers or engines that have not yet implemented the specification.
In an ideal CSS architecture, developers could perform feature queries directly within stylesheet rules using conditional syntax such as:
@supports at-rule(@media; display-mode: picture-in-picture)
/* DPIP supported */
However, evaluating media query capabilities natively within CSS feature queries via the at-rule() function is not currently practical for cross-browser deployments. Broad support for at-rule() parsing remains limited; while Chrome initially introduced implementation work, plans to support query preludes like (display-mode: picture-in-picture) within @supports were largely abandoned.
Browser support for feature detection capabilities remains in active flux across vendors. Release notes for Safari Technology Preview 251 indicated experimental work toward at-rule detection inside @supports, while Firefox 155 announced support for the feature shortly after initial implementations surfaced. Nevertheless, until unified cross-engine support settles, developers cannot rely on pure CSS conditional blocks to determine if DPIP capabilities are present.
As a result, feature detection must be handled programmatically in JavaScript. Developers must check for the presence of the documentPictureInPicture property on the global window object. If the interface is absent, user interface controls intended to trigger floating windows can be cleanly hidden or removed from the DOM:
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 instantiating a Document Picture-in-Picture window, developers interact with the window.documentPictureInPicture.requestWindow() method. This asynchronous method returns a Promise that resolves to a unique Window object representing the detached viewport.
By default, launching a new DPIP window automatically replaces any active DPIP window previously opened by the application. However, managing user interactions requires evaluating how subsequent trigger clicks behave. If a user clicks an initiation button a second time while a floating window is already active, developers can explicitly close the active window, effectively turning the trigger into a toggle control:
document.querySelector("button").addEventListener("click", async () =>
/* If the DPIP window is open, close it */
if (window.documentPictureInPicture.window)
window.documentPictureInPicture.window.close();
);
Developer UX considerations complicate simple toggle controls, however. When a DPIP window opens, operating system focus immediately shifts to the new floating window. Re-triggering the toggle button from the main window may require multiple user clicks to re-focus the primary document before registering the action. Furthermore, native browser UI frames already embed a standardized "Close" icon inside DPIP window borders, rendering redundant custom close buttons inside the floating document unnecessary.
Allowing subsequent clicks on the primary trigger button to simply re-instantiate the floating window offers distinct structural advantages. If a user has moved or resized the floating window across their screen, calling requestWindow() again with explicit geometry configuration will reset the widget to its default dimensions and initial placement.
The requestWindow() method accepts several configuration options:
widthandheight: Define the initial pixel dimensions of the detached window frame. These properties must be declared together; defining one without the other causes the browser to ignore explicit sizing and fall back to default system window dimensions.preferInitialWindowPlacement: When set totrue, this boolean flag instructs the browser not to persist or restore the window’s previous user-adjusted size and position upon re-opening, ensuring a predictable initial layout.disallowReturnToOpener: When set totrue, this option hides the browser’s native "Back to tab" button within the window frame header, which normally closes the floating window and focuses the originating browser tab.
/* Create the DPIP window */
const DPIP = await window.documentPictureInPicture.requestWindow(
width: 600,
height: 400,
preferInitialWindowPlacement: true
);
Once the child window Promise resolves, developers must populate the newly spawned document with content. Transferring a component—such as an interactive stock ticker—from the primary document to the child window body involves standard DOM node manipulation using .cloneNode(true) or direct DOM migration.
/* 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));
A common technical pitfall when rendering components into isolated window contexts is the loss of stylesheet dependencies. Simply cloning an HTML node transfers raw markup into the new window’s <body>, but leaves behind the global styles, variables, and external stylesheets defined in the parent document’s <head>.
To preserve visual fidelity, developers must query all active <style> elements and <link rel="stylesheet"> resources from the parent document and clone them into the DPIP document’s <head>. To maximize rendering performance and avoid triggering multiple expensive browser layout reflows during initialization, all cloned style nodes should be collected into an off-screen DocumentFragment before performing a single, batched insertion:
/* 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 architectural components yields a complete client-side implementation for initializing and populating floating document 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);
);
It is also important for developers testing these implementations to note that Picture-in-Picture window creation is restricted in nested browsing contexts. Opening a DPIP window from within an <iframe> (such as embedded code sandboxes like CodePen) will fail due to security policies; testing requires running applications directly in top-level browsing frames.
Handling the CSS
Extracting an HTML component from its original document hierarchy and inserting it into a detached floating window presents specific CSS challenges. Web developers must account for structural context shifts, as CSS selectors relying on specific ancestor elements or parent wrapper classes may break once the element is rendered in an isolated document <body>.
To apply visual adjustments specifically tailored to the floating presentation context, developers can utilize the @media (display-mode: picture-in-picture) media feature query. This query allows styles to adapt dynamically based on whether the component is currently residing inside a standard tab viewport or inside a Document Picture-in-Picture frame.
For instance, a stock ticker component might feature rounded borders and fixed fitting widths when embedded within a main page layout, but require edge-to-edge full fluid dimensions and squared top corners when detached into a floating pop-out window:
#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;
A crucial distinction within the CSS specification involves the syntax used to target floating interfaces. The :picture-in-picture pseudo-class applies exclusively to media elements isolated via the older, media-specific Picture-in-Picture API. It does not apply to elements rendered inside a Document Picture-in-Picture window. To target DOM elements within the Document Picture-in-Picture API context, developers must exclusively use the @media (display-mode: picture-in-picture) media query.
Wrapping up
The Document Picture-in-Picture API introduces a streamlined event model for tracking window instances. Developers can monitor window state changes using the API’s enter event, which fires on the documentPictureInPicture object when a floating document window successfully opens:
documentPictureInPicture.addEventListener("enter", (event) =>
/* DPIP window opened */
);
This event operates distinctly from the enterpictureinpicture event, which is reserved strictly for tracking video elements in the traditional media PiP API.
With Firefox 151 joining Google Chrome in supporting the specification, the Document Picture-in-Picture API provides desktop web developers with a standardized method for building complex floating utilities. While Apple’s Safari engine has not yet integrated the API into production releases, the growing adoption across major browser engines offers a solid foundation for creating flexible, persistent web widgets across modern desktop environments.