What Window Controls Overlay includes

Window Controls Overlay is a set of four related features that let a PWA place its own content in the title bar area next to the minimize, maximize, and close buttons:

  1. The "window-controls-overlay" value for the "display_override" field in the web app manifest.
  2. The CSS environment variables titlebar-area-x, titlebar-area-y, titlebar-area-width, and titlebar-area-height.
  3. The standardization of the previously proprietary CSS property -webkit-app-region as app-region, used to define draggable regions in web content.
  4. A mechanism to query and work around the window controls region via the windowControlsOverlay member of window.navigator.

What the overlay changes

In a normal installed PWA, the title bar is a full-width, browser-controlled strip. Window Controls Overlay replaces that with a compact overlay holding only the window control buttons, freeing the rest of the former title bar space for your own HTML. This is how platform-specific apps like the macOS Podcasts app achieve their integrated, custom title bar looks — and it’s the mechanism that lets PWAs do the same.

Opting in via the manifest

To enable the overlay, add "window-controls-overlay" as the first entry of the "display_override" array in the manifest:

{
  "display_override": ["window-controls-overlay"]
}

The overlay only appears when all of these conditions are met:

  1. The app runs in its own PWA window, not in a browser tab.
  2. The manifest declares "display_override": ["window-controls-overlay"].
  3. The app runs on a desktop operating system.
  4. The current origin matches the origin the PWA was installed from.

When enabled, the result is an empty title bar with the regular window controls positioned per the operating system convention.

Placing content in the title bar

Once the space exists, you can move content into it. In the Wikimedia Featured Content PWA demo, a search widget for article titles is the element that gets promoted into the title bar. The search feature’s HTML is a simple div containing an input and a label.

<div class="search">
  <img src="logo.svg" alt="Wikimedia logo." width="32" height="32" />
  <label>
    <input type="search" />
    Search for words in articles
  </label>
</div>

The CSS then positions that div into the overlay area using the environment variables provided by the feature:

.search {
  /* Make sure the `div` stays there, even when scrolling. */
  position: fixed;
  /**
   * Gradient, because why not. Endless opportunities.
   * The gradient ends in `#36c`, which happens to be the app's
   * `<meta name="theme-color" content="#36c">`.
   */
  background-image: linear-gradient(90deg, #36c, #131313, 33%, #36c);
  /* Use the environment variable for the left anchoring with a fallback. */
  left: env(titlebar-area-x, 0);
  /* Use the environment variable for the top anchoring with a fallback. */
  top: env(titlebar-area-y, 0);
  /* Use the environment variable for setting the width with a fallback. */
  width: env(titlebar-area-width, 100%);
  /* Use the environment variable for setting the height with a fallback. */
  height: env(titlebar-area-height, 33px);
}

Because the overlay content is ordinary HTML, the title bar is fully responsive. Resizing the PWA window reflows that content exactly as it would in the document body.

Making the title bar draggable

There’s a catch after moving content up: the window itself may no longer be draggable, since only the window control buttons remain as natural drag handles. The fix is the app-region CSS property with the value drag. In the demo, everything except the input element is marked draggable, so users can reposition the window by dragging the surrounding div, img, or label, while the input stays interactive for text entry.

/* The entire search `div` is draggable */
.search {
  -webkit-app-region: drag;
  app-region: drag;
}

/* …except for the `input`. */
input {
  -webkit-app-region: no-drag;
  app-region: no-drag;
}

Feature detection and geometry queries

Support for the feature is detected by testing for the existence of the windowControlsOverlay property:

if ('windowControlsOverlay' in navigator) {
  // Window Controls Overlay is supported.
}

A platform complication emerges because window controls appear on the right on some operating systems and on the left on others—and the Chrome “three dots” menu moves too. For the gradient background in the demo title bar, the direction of the gradient must adapt so it blends with the maroon theme color set via <meta name="theme-color" content="maroon">. That’s done by calling the getTitlebarAreaRect() API on navigator.windowControlsOverlay and choosing the appropriate CSS class dynamically.

if ('windowControlsOverlay' in navigator) {
  const { x } = navigator.windowControlsOverlay.getTitlebarAreaRect();
  // Window controls are on the right (like on Windows).
  // Chrome menu is left of the window controls.
  // [ windowControlsOverlay___________________ […] [_] [■] [X] ]
  if (x === 0) {
    div.classList.add('search-controls-right');
  }
  // Window controls are on the left (like on macOS).
  // Chrome menu is right of the window controls overlay.
  // [ [X] [_] [■] ___________________windowControlsOverlay [⋮] ]
  else {
    div.classList.add('search-controls-left');
  }
} else {
  // When running in a non-supporting browser tab.
  div.classList.add('search-controls-right');
}

Instead of keeping the background image in a static .search rule, the code now toggles between two classes based on the overlay’s geometry.

/* For macOS: */
.search-controls-left {
  background-image: linear-gradient(90deg, #36c, 45%, #131313, 90%, #36c);
}

/* For Windows: */
.search-controls-right {
  background-image: linear-gradient(90deg, #36c, #131313, 33%, #36c);
}

Visibility and resize handling

The overlay won’t be visible in every scenario—not when the PWA is running in a tab, and not on browsers that don’t support the feature. Two detection paths exist: the visible property of windowControlsOverlay, or the display-mode media query (which can be used in both JavaScript and CSS).

if (navigator.windowControlsOverlay.visible) {
  // The window controls overlay is visible in the title bar area.
}
// Create the query list.
const mediaQueryList = window.matchMedia('(display-mode: window-controls-overlay)');

// Define a callback function for the event listener.
function handleDisplayModeChange(mql) {
  // React on display mode changes.
}

// Run the display mode change handler once.
handleDisplayChange(mediaQueryList);

// Add the callback function as a listener to the query list.
mediaQueryList.addEventListener('change', handleDisplayModeChange);
@media (display-mode: window-controls-overlay) { 
  /* React on display mode changes. */ 
}

For one-off queries like picking the right background gradient, getTitlebarAreaRect() suffices. When you need finer control—for instance, adapting content as space grows or shrinks—listen for the geometrychange event via navigator.windowControlsOverlay.ongeometrychange or an event listener. That event fires only when the overlay is visible.

const debounce = (func, wait) => {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

if ('windowControlsOverlay' in navigator) {
  navigator.windowControlsOverlay.ongeometrychange = debounce((e) => {
    span.hidden = e.titlebarAreaRect.width < 800;
  }, 250);
}

You can also register a listener on the windowControlsOverlay object itself rather than assigning a function to the ongeometrychange property.

navigator.windowControlsOverlay.addEventListener(
  'geometrychange',
  debounce((e) => {
    span.hidden = e.titlebarAreaRect.width < 800;
  }, 250),
);

Graceful degradation

Consider two compatibility cases: an app running in a browser that supports the feature but is used in a tab, and an app running in a browser without any support. In both cases, the HTML intended for the overlay will display inline like ordinary content, with fallback positioning values used from the env() variables. On supporting browsers, you can check the visible property and hide that content when the overlay is not active.

On non-supporting browsers, "display_override" is either ignored entirely or the unrecognized "window-controls-overlay" value causes the browser to fall back to the next available display mode, such as "standalone".

Design constraints to keep in mind

Avoid placing classic drop-down menus in the overlay area. On macOS specifically, that pattern would conflict with the platform’s convention that user-expectable menu bars sit at the top of the screen.

Also consider fullscreen behavior. If your app offers a fullscreen view, decide whether the overlay should be included in that view; you may need to rearrange your layout in response to the onfullscreenchange event.

The full demo is available to try in both installed and uninstalled states, and its source code is on GitHub.

Trust and platform boundaries

The Window Controls Overlay API was designed with the same principles outlined in Chromium's Controlling Access to Powerful Web Platform Features document: user control, transparency, and ergonomics. But giving sites partial control over the title bar touches a region that users historically could trust to be browser-owned, so the API has to contend with a few specific risks.

Spoofing the title bar

On initial launch, a standalone PWA shows the page title on the left side of the title bar and the origin on the right, followed by the settings menu and window controls. The origin text disappears after a few seconds. When the browser is set to a right-to-left (RTL) language, the layout is mirrored—origin text sits on the left. Without adequate spacing, an app could position content near that origin (or the spot it occupied) and use padding to append something like evil.ltd to google.com in an overlay, letting users mistake an unsafe origin for a trusted one.

The countermeasure is straightforward: browsers keep the origin visible on launch, and in RTL configurations they enforce enough padding to the right of the origin text so malicious sites can't visually fuse an untrusted origin with a trusted one.

Fingerprinting surface

Feature detection is the main concern here, though the draggable regions and overlay themselves reveal notable platform details. Because window control buttons are positioned differently across operating systems, calling navigator.windowControlsOverlay.getTitlebarAreaRect() returns a DOMRect whose geometry effectively broadcasts the host OS. Developers could already derive that from the user agent string, but UA strings are undergoing freeze efforts, making this a secondary vector worth watching.

Current thinking is that window control sizes remain stable enough across OS releases that they won't reveal minor version numbers. Still, the exposure is limited: it applies only to installed PWAs that choose the custom title bar, and it doesn't surface in normal browser tabs or inside iframes embedded within a PWA—the navigator.windowControlsOverlay API is simply unavailable there.

Cross-origin navigation

An installed PWA that qualifies for the overlay only keeps the custom title bar while it stays on its origin. Navigating to a different origin triggers the fallback to the normal standalone title bar with its black bar, even if the destination would independently satisfy all criteria. Returning to the app's original origin re-enables the overlay.

A black URL bar for out-of-origin navigation.
A black bar is shown when the user navigates to a different origin.

Reporting and next steps

Chromium's team is actively collecting input on the Window Controls Overlay API. For design questions—missing properties, unexpected behavior, security model concerns—the spec issue tracker on the GitHub repo is the right place. Implementation bugs or spec deviations go to new.crbug.com, where you should file under UI>Browser>WebAppInstalls in the Components box and include reproduction steps.

The team also weighs public support when prioritizing work, so developers planning to use the API can signal its importance—to Chromium and to other browser vendors—by posting on X with the #WindowControlsOverlay hashtag and tagging @ChromiumDev, noting where and how the API is being applied.

For deeper reading, these resources are current:

The API was implemented and specified by Amanda Baker of the Microsoft Edge team, and the document was reviewed by Joe Medley and Kenneth Rohde Christiansen.