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.
Opting in
Section titled “Opting in”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).
The tab_strip member
Section titled “The tab_strip member”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. Itsscope_patternssub-property lists URL patterns (resolved against the manifest URL) that define the home tab’s scope; ifhome_tabis present butscope_patternsis absent, the home tab still exists but is scoped to juststart_url.new_tab_button— describes a UI affordance that opens a new tab at itsurlmember (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 thaturlfalls 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" } }}Where it is supported
Section titled “Where it is supported”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.
Feature detection
Section titled “Feature detection”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.
Practical checklist
Section titled “Practical checklist”- Always pair
display_override: ["tabbed"]with a supporteddisplayfallback (e.g.standalone), since unsupported browsers fall through to it automatically. - Only set
tab_stripwhen using thetabbeddisplay mode — it has no effect otherwise. - If you want a persistent pinned tab with its own navigable scope, configure
home_taband itsscope_patterns; otherwise the home tab (if any) is scoped tostart_urlalone. - Set
new_tab_button.urlto a path outside the home tab’s scope, or the new-tab affordance will be hidden. - Treat
tabbedas experimental: confirm current browser support before shipping it as anything but a progressive enhancement.
Where to go next
Section titled “Where to go next”- Manifest display modes — the
displaymember and its fallback chain, whichdisplay_overridesits in front of. - display_override for window-controls-overlay —
the other documented
display_overrideuse case in this reference.