Document Picture-in-Picture is a window, not a video
Dex Halloran
For a decade the phrase "picture-in-picture" meant one thing: a little video that follows you around while you tab away to argue about something on Mastodon. That's it. That was the whole surface. If you wanted a persistent widget — a stock ticker, a timer, a chat pane, a build-status thing — you were writing a browser extension or shipping an Electron app for what should have been ten lines of HTML.
Daniel Schwarz's tutorial on CSS-Tricks is the reminder that the ceiling moved. The Document Picture-in-Picture API opens a floating always-on-top window and drops a whole Document into it. Not a <video>. A document. Your document, styled with your CSS, driven by your JavaScript. That's the news, and it changes what "widget" is allowed to mean on the web.
The method that unlocks the door
The entry point is window.documentPictureInPicture.requestWindow(). It's a promise, it wants a gesture, and it takes a small options bag:
const DPIP = await window.documentPictureInPicture.requestWindow({
width: 600,
height: 400,
preferInitialWindowPlacement: true
});
Two things worth calling out. width and height come as a pair — Schwarz notes you have to set them together, so no cheeky "just tell me the height." preferInitialWindowPlacement tells the browser to stop being helpful with remembered position and size, which is what you want the first time somebody opens the thing. There's also disallowReturnToOpener, which hides the "Back to tab" button when your UX doesn't need it.
You get one open DPIP window per page. To check if it's already there, look at documentPictureInPicture.window. To close it, call window.close() on that reference. There's an enter event on documentPictureInPicture that fires when a window opens — handy when your setup code has to run on the child document, not the parent.
The rad part is also the trap
Here's where Schwarz's piece earns its keep. The DPIP window is a fresh, empty document. Empty as in: none of your stylesheets are there. You clone them across yourself.
The tutorial does it with a querySelectorAll() over <style> and <link rel=stylesheet>, clones the nodes into a document fragment, and appends the fragment to the DPIP document's <head>. Same shape for the widget markup — grab the element in the main page, cloneNode() it, drop it in the child. The example he builds is a stock ticker that lives in the page, then teleports into the floating window when the user asks for it.
And then the caveat that's going to catch every single one of you first time out: taking an HTML component out of context can break the CSS. Descendant selectors that assumed a wrapper. Container queries whose container just vanished. Custom properties inherited from somewhere five ancestors up. Schwarz says it plainly and he's right — if your component only works because its grandparent set a --color, that grandparent is not coming with you.
The escape hatch the API hands you is a targeted media query: @media (display-mode: picture-in-picture). Write PiP-aware styles in the same sheet, then let the query switch the layout to something that survives without its usual scaffolding. It's the same energy as @media (prefers-color-scheme: dark) — you're not forking your CSS, you're annotating it for a new context.
Where the ground still shifts
Support is a mixed bag right now, and I'm going to be honest about it because the post is. Schwarz writes that Firefox 151 recently shipped the API. Chrome supports it. Safari doesn't. That's the picture — one engine out, two in, so anything you build here needs a plan for "the button isn't there today."
Feature-detect and move on. 'documentPictureInPicture' in window is enough to decide whether the "pop out" affordance renders at all. Don't hide the widget when DPIP is missing — hide the pop-out. The widget worked as an in-page component before you added the API call, and it should keep working after.
What I'd actually build with it first
Not a stock ticker. (Sorry, Daniel — the example is fine, it's just that we've all done financial dashboards to death.) I'd reach for this the next time I want a build-log tail, a Pomodoro that survives me switching tabs to procrastinate, or a lyrics pane while I code to something loud enough to worry the neighbours. Anywhere a browser extension used to be the only answer and it felt like a lot of ceremony for what you wanted.
The verdict: the API is small, the mental model is clean (it's a window; you own the DOM inside it), and the CSS caveat is exactly the kind of thing that stops being interesting after the second widget you ship. One engine gap keeps this out of "use it in production tomorrow" territory, but the shape of the thing is right. Solid 8/10 — points off only for Safari, and one back for making the fix a media query I already know how to write.
Source: CSS-Tricks (css-tricks.com)