Skip to content

Tabbed application mode: the tabbed display_override value

In one line: tabbed is an experimental display_override value, defined by the WICG Manifest Incubations proposal and documented by Chrome for Developers as shipped on ChromeOS, that gives an installed PWA its own tab strip; the accompanying tab_strip manifest member lets an app customize that strip with a home_tab and a new_tab_button.

Because tabbed is a new display mode, it cannot be requested through the display member alone — per MDN, display_override lets a developer supply an ordered fallback chain the browser considers before falling back to display, which is exactly how a novel mode like tabbed stays backwards-compatible with browsers that don’t support it:

{
"display": "standalone",
"display_override": ["tabbed"]
}

If the browser doesn’t support tabbed, it falls through to the declared display value (standalone in the example above).

tab_strip is only used when the display mode is tabbed. Per the WICG explainer, if tab_strip is unspecified it defaults to:

{
"tab_strip": {
"new_tab_button": { "url": "<start_url>" }
}
}
  • home_tab — a pinned tab that, when enabled, is always present while the app is open; navigation to a URL within its scope stays in the home tab, while navigation to a URL outside its scope opens in a new app tab. Its scope_patterns sub-property lists URL patterns (resolved against the manifest URL) that define the home tab’s scope; if home_tab is present but scope_patterns is absent, the home tab still exists but is scoped to just start_url.
  • new_tab_button — describes a UI affordance that opens a new tab at its url member (a URL relative to the manifest URL, within the processed manifest’s scope). Per the explainer, the app only has a new tab button if that url falls outside the home tab’s scope; if it falls inside the home tab’s scope, the button is hidden.
{
"display": "standalone",
"display_override": ["tabbed"],
"tab_strip": {
"home_tab": {
"scope_patterns": [{ "pathname": "/dashboard/*" }]
},
"new_tab_button": { "url": "/new" }
}
}

Per the WICG explainer and the Chrome for Developers page, tabbed is an experimental display mode, part of the WICG Manifest Incubations proposal. The Chrome for Developers page documents it as shipped on ChromeOS specifically. Developers should check current browser and platform support before relying on it in production.

Once launched, an app can check which display mode actually applied at runtime with window.matchMedia():

if ('matchMedia' in window && window.matchMedia('(display-mode: tabbed)').matches) {
// Running in tabbed mode — safe to render tab-strip-aware UI.
} else {
// Fell back to a non-tabbed display mode (e.g. standalone) —
// don't assume a tab strip exists.
}

A browser that doesn’t recognize tabbed in display_override silently skips it and applies display (standalone in the manifest example above) instead; the matchMedia() check above is how the running app confirms which mode it actually got.

  • Always pair display_override: ["tabbed"] with a supported display fallback (e.g. standalone), since unsupported browsers fall through to it automatically.
  • Only set tab_strip when using the tabbed display mode — it has no effect otherwise.
  • If you want a persistent pinned tab with its own navigable scope, configure home_tab and its scope_patterns; otherwise the home tab (if any) is scoped to start_url alone.
  • Set new_tab_button.url to a path outside the home tab’s scope, or the new-tab affordance will be hidden.
  • Treat tabbed as experimental: confirm current browser support before shipping it as anything but a progressive enhancement.