Desktop Platforms¶
On the desktop, an installed Progressive Web App runs in its own window and registers with the operating system like a native app: a Start menu or Launchpad entry, a taskbar or Dock icon, file and protocol associations, a jump list, a badge and optionally a login item. Chromium browsers (Chrome and Edge) provide that integration on Windows, macOS, Linux and ChromeOS from the same code base, Safari adds any site to the Dock on macOS Sonoma and later, and Firefox has pinned web apps on Windows since Firefox 143. This page shows what each browser writes into each operating system, how Windows, macOS, Linux and ChromeOS differ, how to design for desktop windows, keyboards and multiple windows, and when Electron or Tauri is the better choice.
Key takeaways
- Chromium is the complete desktop PWA platform. Chrome and Edge create real OS entries on Windows, macOS and Linux (Start menu shortcut, app shim bundle or
.desktopfile), register file and protocol handlers, show shortcuts in the jump list or Dock menu, and badge the icon, except on Linux. - Installed Chromium apps live in a browser profile. They share cookies and storage with that profile's tabs, need the browser installed, and are keyed by an app ID derived from the manifest
id. - Safari's Mac web apps are separate. File > Add to Dock works for any site, copies cookies once, then keeps its own storage, and uses a subset of the manifest. There's no install event and no file or protocol handling.
- Firefox desktop web apps exist only on Windows by default. They're pinned taskbar windows (Firefox 143+, and 150+ for the Microsoft Store build) keyed by a Firefox-generated ID, with no install prompt and limited manifest use. Linux has them behind a preference; macOS doesn't.
- ChromeOS is the most integrated desktop. It's the only desktop where
share_targetand tabbed mode ship, and the first platform for Isolated Web Apps. - Distribution goes beyond the browser. PWABuilder packages a PWA for the Microsoft Store, admins force-install apps by policy, and
WebAppSettingscan make an app run at login.
Who installs what on the desktop¶
| Windows | macOS | Linux | ChromeOS | |
|---|---|---|---|---|
| Chrome | App window, Start menu shortcut, Installed apps entry | App shim bundle in ~/Applications/Chrome Apps.localized/ | .desktop file in the user's applications directory | System app in the launcher and shelf |
| Edge | As Chrome, plus Microsoft Store packages and auto-start option at install | As Chrome, in Edge's own apps folder | As Chrome | Not applicable |
| Safari | Not applicable | Add to Dock web app (Safari 17+, macOS Sonoma+) | Not applicable | Not applicable |
| Firefox | Taskbar web app (143+) | ❌ | Behind browser.taskbarTabs.enabled (off by default) | Not applicable |
Support data as of September 2026. The install flows and menu labels for each browser are listed on Installation by Platform; the criteria that decide whether a browser promotes installation are on Installability Criteria.
How Chromium installs a desktop app¶
Chrome and Edge share Chromium's web app system. Whatever the OS, an install goes through the same stages:
flowchart TD
A["User accepts install (address bar, menu, or prompt())"] --> B["Fetch and validate manifest, download icons"]
B --> C["Compute app ID from manifest id"]
C --> D["Write app to the profile's web app database"]
D --> E["OS integration"]
E --> E1["Launcher entry: shortcut, app shim or .desktop file"]
E --> E2["File handlers and protocol handlers"]
E --> E3["Shortcuts menu: jump list, Dock menu, desktop actions"]
E --> E4["Uninstall registration (Windows)"]
E --> E5["Run on OS login, if enabled"]
D --> F["Open start_url in an app window"] Things that hold on every desktop OS:
- The app belongs to a browser profile. Launching it starts that browser with the profile and the app ID (
--profile-directory=… --app-id=…on the command line). Cookies, IndexedDB, Cache Storage and service workers are the profile's, so a user signed in in a tab is signed in in the app. Installing the same app in two profiles creates two OS entries. - The app ID is a hash. Chromium turns the manifest
idinto a 32-character ID over the lettersatop. It appears inchrome://web-app-internals, in shortcut file names and bundle identifiers, and inchrome://app-settings/<app-id>. App Identity & Updates shows how to compute it. - Any page can be installed. Since 2024 Chrome offers Cast, save, and share > Install page as app for pages that don't meet the criteria, building the app from the title and favicon. The criteria decide promotion and quality, not possibility.
- Updates are fast on desktop. Since Chrome 144, any page load that links a manifest with a matching
idtriggers an update check without a daily throttle. Non-security fields apply silently; name and icon changes wait for the user to approve them. - Uninstalling keeps data unless the user opts in. The uninstall dialog has an opt-in checkbox to delete the site's browsing data.
- Installed apps can move to a new same-site origin. From Chrome 150, desktop Chrome can migrate an installed PWA to a new same-site origin while keeping the install, instead of forcing users to uninstall and reinstall. The mechanism is on App Identity & Updates.
The app window itself depends on the display mode: standalone (title bar with the app name and an app menu), minimal-ui (adds back and reload buttons), Window Controls Overlay (your content in the title bar), or tabbed (ChromeOS). The app menu in the title bar offers Open in Chrome (or Edge), app info, permissions, Uninstall and, in Edge, pinning commands. Out-of-scope navigations show an origin bar under the title bar. Display Modes covers the resolution rules.
Windows¶
Windows is where desktop PWAs look most like native apps: they appear in Start, on the taskbar, in Alt+Tab, in Settings > Apps > Installed apps, and optionally in the Microsoft Store.
What installation writes¶
From Chromium's Windows integration code:
- A Start menu shortcut. It targets
chrome_proxy.exe(Chrome's small proxy executable, next tochrome.exe) rather than Chrome itself, with the profile and app ID as arguments. Chromium's comment explains the proxy exists so that pinning from Start uses the correct icon. The shortcut carries an AppUserModelID unique to the app and profile, which is how Windows groups the app's windows under its own taskbar button instead of Chrome's. - An icon file generated from your manifest icons, stored in the profile's web app data directory.
- An Installed apps entry, so users can uninstall from Windows Settings. Chromium registers apps with the OS uninstall list only on Windows.
- Optional shortcuts on the desktop and the taskbar, depending on the install dialog's options.
- Taskbar pins that follow renames. When
namechanges, Chromium renames matching.lnkfiles in the taskbar pin directory, because Windows derives a pinned icon's label from the shortcut's file name.
Edge does the same. Its install flow adds a Pin to Start checkbox and, in the post-install dialog, an Auto-start on device login checkbox, and the app's Settings and more menu has an App info panel with pinning commands, App settings, privacy details and extensions.
Jump lists and badges¶
The manifest shortcuts member becomes the app's jump list (right-click on the taskbar button or Start tile). Chromium parses the first ten entries, and Windows shows up to ten. App Shortcuts covers ordering and icons.
navigator.setAppBadge() renders a small overlay on the taskbar button. Chromium shows numbers up to 99 and 99+ above that; setAppBadge() without an argument shows a dot. See Badging API.
File and protocol handler registration¶
File Handling and Protocol Handlers & Launch Handling cover the APIs. On Windows the registration works like this:
- Chromium creates an app-specific launcher executable named after your app (for example
Tunebox.exe) in the app's data directory, as a hard link to (or copy of) the genericchrome_pwa_launcher.exe. Windows shows that file name in Open with lists and default-app settings. - Each registration gets a ProgId: the browser's base app ID plus a hash of the profile directory, the app ID and, for file handlers, the handled extensions. The ProgId points at the launcher with the profile and app ID. Because the mapping is written to the user's registry, Chromium's source warns that the hash scheme can't change without disrupting existing installs.
- File associations use extensions only. Windows ignores MIME types, so every
acceptentry needs extensions. Windows launches the app once per file even when the user opens several. - Nothing becomes the default automatically. Your app is added to Open with. The first launch through a file type or protocol asks the user for permission, with a Remember this choice option.
Window Controls Overlay¶
Windows is where WCO matters most, because Windows apps traditionally draw their own title bars. With "display_override": ["window-controls-overlay"], Chrome and Edge 105+ hide the title bar and leave only the caption buttons (minimize, maximize, close) plus the app menu in an overlay, and your page gets the rest of the title bar area through env(titlebar-area-*) and navigator.windowControlsOverlay. The full implementation, including draggable regions, is on Window Controls Overlay.
Publishing to the Microsoft Store¶
The Microsoft Store accepts PWAs as Windows app packages. Microsoft's documented flow:
- Reserve a product name in Partner Center (Apps and games > New product > MSIX or PWA app). Microsoft's page requires a personal Microsoft account enrolled in the Windows Developer Program.
- Copy the Package ID, Publisher ID and Publisher display name from Product management > Product Identity.
- Enter your URL in PWABuilder, choose Package For Stores > Windows, and paste the three values. The download contains an
.msixbundleand a.classic.appxbundle, which together cover a wide range of Windows versions. - Submit both packages in Partner Center. Microsoft says review typically takes 24 to 48 hours.
What you need to know about Store-installed PWAs:
- Web code updates don't need a new package. HTML, CSS, JavaScript and the service worker come from your server as usual.
- Manifest changes do. Microsoft states that the manifest data "is copied to the Windows app package", so changing the icon, name,
file_handlers,protocol_handlersorshare_targetmeans generating and submitting a new package. - You can measure Store installs. On first launch, Edge sends
Referer: app-info://platform/microsoft-storewith the first navigation, also visible asdocument.referrer. - The start URL is fixed. A Store package's start URL is hard-coded, so a redirect to a locale-specific domain is out of scope and shows the URL bar. Microsoft notes this can't currently be suppressed for Store apps.
Publishing to App Stores and PWABuilder cover packaging options in depth.
Run on OS login¶
Users can make an installed app start when they sign in: in Chrome from chrome://apps (right-click, Start app when you sign in), in Edge from edge://apps (Auto-start on device login) or the post-install dialog. Chrome's announcement lists Chrome 91 and Edge 91 on Windows, macOS and Linux. There's no API for this: Chrome's documentation says installed apps "will not be permitted to automatically enable themselves" and "a manual user gesture will always be required".
Administrators can set it with the WebAppSettings policy (Chrome 102+ on Windows, macOS and Linux, ChromeOS 120+), keyed by manifest ID:
[
{ "manifest_id": "https://app.example.com/", "run_on_os_login": "run_windowed",
"prevent_close_after_run_on_os_login": true },
{ "manifest_id": "https://chat.example.com/", "run_on_os_login": "allowed" },
{ "manifest_id": "*", "run_on_os_login": "blocked" }
]
allowed lets the user choose, blocked prevents it, and run_windowed forces it on. prevent_close_after_run_on_os_login (Chrome 117+) stops the user from closing an app that was started at login, and force_unregister_os_integration (Chrome 118+) removes an app's shortcuts, file handlers and protocol handlers on Windows, macOS and Linux. Force-installation itself uses the WebAppInstallForceList policy.
Widgets and other Edge-only features¶
Edge defines a widgets manifest member and a service worker self.widgets API that render Adaptive Cards templates in the Windows 11 Widgets Board. Microsoft's documentation for it dates from 2023, requires Windows App SDK 1.2 and Windows Developer Mode for local testing, and describes shipping widget-enabled PWAs through the Microsoft Store. No other browser implements it, and there's no standards-track proposal behind it. Treat it as an Edge-only experiment and verify its state in current Edge before building on it. The member reference is on Advanced & Integration Members.
How the pieces fit, per Microsoft's documentation:
- Widgets aren't HTML. Each entry in
widgetspoints to an Adaptive Cards template (ms_ac_template, required) and optional JSON data (data,type).name,description,tag,screenshotsandms_ac_templateare required;templateis required but currently informational.updateis a refresh interval in seconds that your service worker has to honor itself, andmultiple(defaulttrue) allows several instances. - Installing the PWA only offers the widget. A widget instance exists once the user adds it from the Widgets Board, which fires
widgetinstallin your service worker. Nothing is rendered until you callself.widgets.updateByTag()orupdateByInstanceId()with a{ template, data }payload of strings. - Other events:
widgetuninstall,widgetresume(the host resumed rendering after suspending widgets) andwidgetclick, whoseactionis theverbof the Adaptive Card'sAction.Execute.
// Renders a widget from its manifest definition. Guarded so the same service worker
// runs unchanged in browsers without self.widgets.
async function renderWidget(widget) {
const { msAcTemplate, data, tag } = widget.definition;
const [template, payload] = await Promise.all([
fetch(msAcTemplate).then((r) => r.text()),
data ? fetch(data).then((r) => r.text()) : Promise.resolve("{}"),
]);
await self.widgets.updateByTag(tag, { template, data: payload });
}
if ("widgets" in self) {
self.addEventListener("widgetinstall", (event) => {
event.waitUntil(renderWidget(event.widget));
});
self.addEventListener("widgetresume", (event) => {
// The host may have discarded the rendered card while suspended.
event.waitUntil(renderWidget(event.widget));
});
self.addEventListener("activate", (event) => {
// A new service worker version should refresh existing instances.
event.waitUntil(
self.widgets.matchAll({ installed: true }).then((list) =>
Promise.all(list.map((widget) => renderWidget(widget))),
),
);
});
self.addEventListener("widgetclick", (event) => {
if (event.action === "open-app") {
// Microsoft's reference suggests clients.openWindow() here; keep the worker
// alive until it settles when the event supports waitUntil().
const opening = clients.openWindow("/");
if (typeof event.waitUntil === "function") event.waitUntil(opening);
}
});
}
A known Edge limitation: Microsoft documents that PWAs aren't supported in FSLogix environments (virtual desktop profile containers). An installed app disappears after the user signs out and back in.
macOS¶
Chrome and Edge: app shims¶
On macOS, Chromium creates an app shim for each installed app: a small .app bundle that the Finder, Launchpad, Spotlight and the Dock treat as an application. For Chrome, the bundles live in ~/Applications/Chrome Apps.localized/ (Chromium and Chrome Canary use their own folder names; the .localized suffix lets macOS show a localized folder name). Key details from Chromium's source:
- Bundle identifier: the browser's base bundle ID, then
.app., then the app ID, for examplecom.google.Chrome.app.<app-id>. Apps installed in several profiles get a profile-scoped identifier. - The shim launches Chrome. When opened, it connects to (or starts) the browser and asks it to open the app window. The Dock shows the shim's icon and name, and Cmd+Tab switches to it as a separate application.
- File handlers become
CFBundleDocumentTypesentries (extensions and MIME types) in the shim'sInfo.plist, so the app appears in Finder's Open With menu. - Protocol handlers are registered for the shim, so other apps can launch it with your scheme.
- Run on OS login adds the shim to the user's login items.
- Badges appear on the Dock icon. Shortcuts appear in the Dock icon's context menu.
- Notifications are attributed to the app from Chrome 152. Chrome's release notes say an installed PWA's notifications on macOS now show "its own name and icon in Notification Center" rather than Google Chrome. Users therefore manage your app's alert style and banners in System Settings > Notifications separately from Chrome's. Before Chrome 152, every PWA notification appeared under Chrome.
Because the shim is a real bundle, users can find it in Finder and Spotlight like any application. Uninstall from the app's menu or chrome://apps so the browser removes both its records and the shim.
Safari: Add to Dock¶
Safari 17 on macOS Sonoma added web apps on Mac. Users choose File > Add to Dock (or Share > Add to Dock) for any website, edit the name, and get an app in the Dock that "always opens in its own window as a web app, even if the site does not have a manifest file", in WebKit's words. Apple's support article adds that the web app "functions independently of Safari. It shares no browsing history, cookies, website data" with Safari after creation.
How it works in detail:
- Creation copies cookies once. WebKit: "Safari will copy the website's cookies to the web app", so cookie-based sessions survive. No other storage is copied: IndexedDB, Cache Storage and
localStoragestart empty. - The manifest customizes it. WebKit's Safari 17 post names display mode, name, theme color and start URL. Safari 17.4 added
shortcuts(in the app's File menu and Dock menu), and Safari 18 usesscopefor link handling. - Links follow scope. Apple's WWDC23 session "What's new in web apps" says in-scope links "open within the web app", out-of-scope links open "in my default browser", and links loaded through
window.open()always open in the web app. Since Safari 18 (macOS Sequoia), links from other apps (Mail, Messages, Slack and so on) that match an installed web app's scope open in the web app instead of the default browser. Without a manifest, WebKit matches the host of the page the web app was created from. - Extensions work inside web apps. Safari 18 lets users enable Safari Web Extensions and content blockers per web app from its Settings; those enabled in Safari are on by default.
- Users control the chrome. The web app's Settings let users change the name, start URL and icon, show or hide navigation controls (back, forward, app name, Share), and choose whether the title bar takes the site's color.
- Web platform features work as in Safari: service workers, Web Push (notifications and a Dock badge for unread notifications), the Badging API, AutoFill from iCloud Keychain, Stage Manager and Mission Control. Permissions (camera, microphone, location, notifications) are granted to the web app separately.
- It lives in
~/Applications. Apple's instructions for deleting one: open the Applications folder in your home folder and drag the web app to the Trash.
There's no beforeinstallprompt, no appinstalled, no file handling, protocol handling, share target or Window Controls Overlay. Guide Mac Safari users to File > Add to Dock with your own instructions UI, as Install Prompts & Custom UI shows.
Chrome app shims versus Safari web apps¶
| Chrome or Edge app shim | Safari web app | |
|---|---|---|
| Install trigger | Browser UI or beforeinstallprompt | Add to Dock, user only |
| Manifest required | For promotion; any page can be installed from the menu | No |
| Storage | Shared with the browser profile | Own container; cookies copied once at creation |
| Updates from the manifest | Automatic (Chrome 144+ silent for most fields) | Not documented; users can edit name, URL and icon |
| File, protocol handlers | ✅ | ❌ |
| Shortcuts | ✅ Dock menu | ✅ Safari 17.4+, File and Dock menus |
| Badging | ✅ | ✅ |
| Custom title bar | ✅ WCO | ❌ (title bar color only) |
| Requires the browser to run | Yes, Chrome launches in the background | Uses the system WebKit |
Linux¶
The .desktop file¶
On Linux, Chromium integrates through the freedesktop.org standards. Its Linux shortcut code writes a desktop entry per app and profile named <browser executable>-<app-id>-<profile directory>.desktop (for Chrome, chrome-<app-id>-Default.desktop), installs it into the applications menu with xdg-desktop-menu (which places it in $XDG_DATA_HOME/applications, usually ~/.local/share/applications), and runs update-desktop-database. A generated entry looks like this (illustrative values):
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Terminal=false
Type=Application
Name=Tunebox
MimeType=application/x-tunebox-playlist;x-scheme-handler/web+tunebox;
Exec=/opt/google/chrome/google-chrome --profile-directory=Default --app-id=abcdefghijklmnopabcdefghijklmnop %U
Icon=chrome-abcdefghijklmnopabcdefghijklmnop-Default
StartupWMClass=crx_abcdefghijklmnopabcdefghijklmnop
Actions=0;1
[Desktop Action 0]
Name=New playlist
Exec=/opt/google/chrome/google-chrome --profile-directory=Default --app-id=abcdefghijklmnopabcdefghijklmnop --app-launch-url-for-shortcuts-menu-item=https://tunebox.example/new
What each key does, according to Chromium's source:
Nameis your app's name, with the URL used as a fallback when the title is empty or contains line breaks (to stop injection of extra keys such asExec).Execlaunches the browser with the profile and app ID.%Uis appended only when the app handles files, so the entry doesn't show up as a handler for every file type.MimeTypelists the MIME types fromfile_handlersplus anx-scheme-handler/<scheme>pseudo-type for eachprotocol_handlersentry. That's how protocol handlers are registered on Linux. Chromium filtersx-scheme-handlertypes out of file handlers so a file handler can't register a protocol by accident. It also installs a shared-mime-info XML file for custom file types.StartupWMClassis derived from the internal app name_crx_<app-id>with underscores trimmed, which givescrx_<app-id>. Desktop environments use it to match the app's windows to the entry, so the dock or panel shows your icon instead of Chrome's.Actionsand the[Desktop Action N]groups come fromshortcuts, which is how GNOME and KDE show them in the launcher's context menu.
Running at login copies the same entry into the XDG autostart directory (usually ~/.config/autostart).
Linux caveats¶
- No badges. MDN notes that "Linux offers no universal badging API on the operating system level", and Chromium resolves
setAppBadge()without doing anything. - No Web Share. Chromium has no Web Share implementation on Linux, so
navigator.shareis undefined. - Stale caches. If a file manager doesn't list your app in Open With, the MIME cache is usually stale; Chromium runs
update-desktop-databasebut ignores failures of that step. - Sandboxed browser packages. When Chrome or Edge runs from a Flatpak or Snap package, the paths above and the
Execline differ, and what the sandbox exposes to the host desktop depends on the package. Test the packaging your users run.
ChromeOS¶
ChromeOS is the desktop where web apps are first-class system apps rather than browser add-ons. Installed PWAs appear in the launcher, on the shelf and in Settings > Apps, next to Android apps from Google Play and Linux apps. On top of the common Chromium features:
- Web Share Target works. ChromeOS is the only desktop where
share_targetis supported (Chrome 89+). Your app appears in the system sharesheet used by the Files app, Chrome and Android apps. ChromeOS filters shared files by MIME type only. - Files app integration. File handlers appear under Open with in the Files app.
- Tabbed mode ships. The
tabbeddisplay mode and thetab_stripmember are stable on ChromeOS only; elsewhere they're behind flags. See Advanced & Integration Members. note_takingregisters an app as a note-taking app for the stylus and lock-screen integrations. It's ChromeOS-only.- Link capturing is off by default. Chromium's re-implemented navigation capturing is available on ChromeOS, but each app's setting defaults to off, unlike Windows, macOS and Linux.
- Managed deployment. Admins force-install web apps with
WebAppInstallForceListand can pin them to the shelf. ChromeOS is also the first platform for Isolated Web Apps, which administrators install by policy. - Play Store TWAs. Because ChromeOS runs Android apps, a Trusted Web Activity published on Google Play can be installed on Chromebooks too. Prefer the PWA itself where you can: it runs natively in Chrome rather than through the Android layer.
Firefox on the desktop¶
Firefox had no desktop installation for years. Firefox 143 (September 16, 2025) added web apps on Windows: its release notes say "Firefox now supports running websites as web apps pinned directly to the taskbar." At launch the feature excluded Firefox installed from the Microsoft Store; Firefox 150 (April 21, 2026) made web apps "available to Windows users who installed Firefox through the Microsoft Store."
Firefox's source documentation (the component is called Taskbar Tabs) describes the rest:
- Platforms: enabled by default on Windows (MSIX and non-MSIX builds), available on Linux (unsandboxed and Flatpak) but disabled by default behind the
browser.taskbarTabs.enabledpreference, and no macOS support. The documentation gives the reason: macOS expects separate applications to run in separate processes, while Firefox web apps share the browser's profile and process. - Creation: the user clicks Add tab to taskbar in the address bar, which opens the site in a new simplified window and prompts to pin it. Remove tab from taskbar reverses it.
- Identity and storage: each web app is recorded in
taskbartabs/taskbartabs.jsonin the profile under a Firefox-generated ID, and is tied to the profile and to the container (Contextual Identity) it was created in. Storage is the profile's, as in Chromium. - Session restore: web app windows behave like private windows for session restore: they aren't reopened with Open previous windows and tabs.
- Manifest use: Firefox reads the scope,
start_urlandnamewhen creating the app if a manifest is present, and otherwise uses the page's origin. MDN's data says these windows matchdisplay-mode: minimal-ui. There's nobeforeinstallprompt,appinstalled,shortcuts, file or protocol handling, and no documented manifest update pipeline.
Service workers, push, notifications and caching work in desktop Firefox tabs as everywhere else, so treat desktop Firefox as a place where your PWA runs well as a website, and possibly as a pinned window on Windows.
Designing for desktop app windows¶
Installed desktop apps are used differently from mobile ones: large and resizable windows, keyboards and mice, several windows side by side, and long sessions. Responsive & Adaptive Design and App-Like UX Patterns cover the general patterns; the points below are specific to desktop app windows.
Window size and position¶
There's no manifest member for the initial window size. Chromium opens a new app window at a default size and remembers each app's last window bounds. Design for a wide range: from a narrow window snapped to a third of the screen to a maximized 4K display. Container queries help components adapt to their pane rather than to the window.
For multi-screen apps (trading, presentations, point of sale), the Window Management API (window.getScreenDetails(), Chromium 100+) returns every connected screen and lets you place windows with window.open() features or fullscreen on a specific screen, after the user grants the window-management permission. See Media & System APIs.
Keyboard shortcuts¶
In a browser tab, Chromium reserves some shortcuts (such as Ctrl+N, Ctrl+T and Ctrl+W) so pages can't take them over. In an app window it doesn't: Chromium's BrowserCommandController says "In Apps mode, no keys are reserved", so a keydown handler that calls preventDefault() gets those combinations. Shortcuts you don't handle fall through to the browser's default command. Users expect native-style shortcuts in an installed app. Map them on keydown, respect the platform's modifier key, and leave standard editing and accessibility shortcuts alone:
// Registers app-level keyboard shortcuts with the platform's primary modifier.
const isMac = /Mac|iPhone|iPad/.test(navigator.platform) ||
navigator.userAgentData?.platform === "macOS";
const bindings = new Map([
["k", () => openCommandPalette()], // Ctrl+K / Cmd+K
["n", () => createDocument()], // Ctrl+N / Cmd+N: not reserved in Chromium app windows
[",", () => openSettings()], // Cmd+, is the macOS convention for settings
]);
window.addEventListener("keydown", (event) => {
const primary = isMac ? event.metaKey : event.ctrlKey;
if (!primary || event.altKey || event.repeat) return;
const action = bindings.get(event.key.toLowerCase());
if (!action) return;
// Leave text fields alone unless the binding is one editors never use (Ctrl/Cmd+K, N, ,).
// Anything you add later that collides with editing (Ctrl+B, Ctrl+Z...) must not fire here.
const target = event.target;
const inTextField =
target instanceof HTMLElement &&
(target.isContentEditable || target.matches("input, textarea, select"));
if (inTextField && !["k", "n", ","].includes(event.key.toLowerCase())) return;
event.preventDefault(); // in an app window, this also suppresses the browser command
action();
});
function openCommandPalette() { document.querySelector("dialog#palette")?.showModal(); }
function createDocument() { /* app logic */ }
function openSettings() { /* app logic */ }
Combinations the operating system handles before the browser (Alt+Tab, Alt+F4 on Windows, Cmd+Tab on macOS) never reach the page, and the same code in a browser tab still can't override reserved shortcuts, so keep a non-conflicting alternative. The Keyboard Lock API (navigator.keyboard.lock()) captures system keys such as Esc or Alt+Tab, but only in fullscreen, so it suits games and remote-desktop clients rather than productivity apps. Show shortcuts in menus and tooltips (aria-keyshortcuts for assistive technology), and document them in a help dialog.
Multiple windows and launch handling¶
Desktop users open several windows of the same app. Decide deliberately how launches map to windows:
launch_handler.client_mode(Chrome 110+) chooses between a new window (navigate-new), reusing and navigating an existing one (navigate-existing), or focusing an existing one and handing it the launch throughwindow.launchQueue(focus-existing).- Link capturing. Chromium on Windows, macOS and Linux captures navigations that would open a new browsing context (new tab or window links such as
target="_blank", and links from other apps) into installed apps by default, and users can switch it off per app. The default is on from Chrome 138 for apps that declarelaunch_handler.client_modeand from Chrome 140 for all apps, after a staged rollout from Chrome 134 (see Chrome's navigation management guide). On ChromeOS it is available but off by default per app. Expect some users on older builds to still get a browser tab. window.open()to an in-scope URL from an app window opens another app window.
Multiple windows share storage and the service worker, so keep them in sync. BroadcastChannel is the simplest tool:
// Keeps open windows of the app consistent: saves in one window refresh the others,
// and the most recently focused window can claim single-instance work.
const channel = new BroadcastChannel("app-sync");
const windowId = crypto.randomUUID();
export function announceSaved(docId, version) {
channel.postMessage({ type: "saved", docId, version, from: windowId });
}
channel.addEventListener("message", ({ data }) => {
if (data.from === windowId) return;
if (data.type === "saved") {
// Another window saved a newer version: reload it here if the user isn't editing it.
document.dispatchEvent(new CustomEvent("remote-save", { detail: data }));
}
});
// Only one window should run periodic work such as polling or sync.
export async function runAsLeader(task) {
if (!navigator.locks) return task(); // very old browsers: just run it
// The lock is held for as long as task() runs; if this window closes, another takes over.
return navigator.locks.request("app-leader", () => task());
}
Protocol Handlers & Launch Handling has a complete launch router, and Messaging & the Clients API covers coordinating windows through the service worker.
Title bar, menus and tabs¶
In standalone, the title bar shows your name and uses theme_color; the page's <meta name="theme-color"> overrides it at run time on desktop Chromium and Safari web apps. With WCO you draw the title bar yourself. Users who want tabs get them in tabbed mode on ChromeOS; elsewhere, design documents as windows or build in-app tabs. There's no API to add items to the OS menu bar on macOS; put commands in your own UI and in shortcuts.
PWAs versus Electron and Tauri¶
Desktop teams often compare a PWA with a packaged app built from the same web code. Electron bundles its own Chromium and Node.js into every app; Tauri uses the operating system's webview (WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux) with a Rust core, and Tauri 2.0, released on October 2, 2024, added iOS and Android.
| Installed PWA | Electron | Tauri | |
|---|---|---|---|
| Rendering engine | The user's browser (Chromium, WebKit or Gecko) | Bundled Chromium, one version per app | OS webview: WebView2, WKWebView, WebKitGTK |
| Download size | None beyond your site's assets | Includes Chromium and Node.js | Small binary; the webview comes with the OS |
| Native access | Web APIs only (File System Access, WebHID, WebUSB and so on, mostly Chromium) | Full Node.js and native modules | Rust commands and plugins, with a permission system |
| Updates | Deploy to your server; users get it on next load | Your own updater (for example autoUpdater); users run old Chromium until they update | Your own updater plugin |
| Engine security patches | Browser vendor's updates | Your responsibility: Electron ships a new major every 8 weeks and supports the latest three | OS updates the webview (on Linux, the distribution) |
| Distribution | Browser install, Microsoft Store, policy | Installers, Microsoft Store, Mac App Store | Installers, stores |
| Cross-engine differences | Real: Safari and Firefox lack many APIs | None: one engine | Real: three different engines |
| Offline, push, background | Service worker features | Anything Node can do | Anything Rust can do |
A PWA is the right choice when web APIs cover your needs: most productivity, communication and content apps. Choose Electron when you need deep OS access (system tray, global shortcuts, native menus, arbitrary file system or process access) with one predictable engine. Choose Tauri for the same needs with a much smaller footprint, accepting per-OS webview differences. Many teams ship both: the PWA for reach, and a wrapper that loads the same site for users who need native extras. If you need privileged capabilities like raw sockets but want to stay on the web platform in managed environments, look at Isolated Web Apps. PWA vs Native vs Hybrid covers the wider decision.
Browser support¶
Support data as of September 2026. For live data, see MDN's Web App Manifest reference and caniuse.
| Feature | Chrome / Edge (Windows) | Chrome / Edge (macOS) | Chrome / Edge (Linux) | ChromeOS | Safari (macOS) | Firefox (Windows) |
|---|---|---|---|---|---|---|
| Install as app | ✅ | ✅ | ✅ | ✅ | ✅ 17 | ✅ 143 |
beforeinstallprompt | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
shortcuts | ✅ 85 | ✅ 96 | ✅ 96 | ✅ 96 | ✅ 17.4 | ❌ |
file_handlers | ✅ 102 | ✅ 102 | ✅ 102 | ✅ 102 | ❌ | ❌ |
protocol_handlers | ✅ 96 | ✅ 96 | ✅ 96 | ⚠️1 | ❌ | ❌ |
share_target | ⚠️2 | ❌ | ❌ | ✅ 89 | ❌ | ❌ |
| Window Controls Overlay | ✅ 105 | ✅ 105 | ✅ 105 | ✅ 105 | ❌ | ❌ |
tabbed display mode | 🧪 flag | 🧪 flag | 🧪 flag | ✅ | ❌ | ❌ |
| Badging | ✅ 81 | ✅ 81 | ⚠️ resolves, no badge | ✅ 91 | ✅ 17 | ❌ |
| Run on OS login | ✅ 91 (user setting) | ✅ 91 | ✅ 91 | ✅ (policy) | ❌ | ❌ |
| Store distribution | ✅ Microsoft Store | ❌ | ❌ | ⚠️ via TWA | ❌ | ❌ |
Common pitfalls¶
- Assuming Safari web apps share Safari's storage. Only cookies are copied, once. Plan a sign-in or data sync path for the first launch.
- Missing extensions in
file_handlers. Windows ignores MIME types; without extensions your app never appears in Open with. - Changing
idorstart_urlwithoutid. Every OS entry, ProgId and bundle identifier is derived from the app ID, so a newidmeans a second app next to the old one. - Relying on badges on Linux. The call resolves and nothing appears.
- Store packages and manifest edits. A Store-installed PWA keeps the manifest data from the package until you submit a new one.
- Hard-coding Ctrl. Use Cmd on macOS and test that your shortcuts don't collide with the OS or assistive technology.
- Ignoring multiple windows. Without
launch_handlerand cross-window sync, users end up with stale windows and conflicting edits. - Designing only for one window size. Snapped, tiled and maximized windows are all normal on the desktop.
Debugging desktop installs¶
chrome://web-app-internals(andedge://web-app-internals) lists every installed app with its app ID, manifest data, OS integration state, and update-check history.chrome://appsandedge://appsshow installed apps with launch, uninstall and Start app when you sign in options.chrome://app-settings/<app-id>shows link capturing, file and protocol handler permissions.- DevTools > Application > Manifest shows the processed manifest, installability errors, the computed app ID, and a Protocol Handlers section where you can test a scheme through the real OS path.
- Windows: check Settings > Apps > Installed apps, the
.lnkfiles in the Start menu folders, and the ProgIds under the current user's registry classes if a handler doesn't register. - macOS: inspect the shim with
plutil -p ~/Applications/Chrome\ Apps.localized/<App>.app/Contents/Info.plistto see document types and URL schemes. Safari web apps can be inspected from Safari's Develop menu. - Linux: read the
.desktopfile, runxdg-mime query default x-scheme-handler/web+yourschemeto see which app owns a scheme, andupdate-desktop-database ~/.local/share/applicationsif menus look stale. - Firefox:
about:configshowsbrowser.taskbarTabs.enabled; the web app registry istaskbartabs/taskbartabs.jsonin the profile directory.
Browser DevTools covers the DevTools panes in detail.
Further reading¶
On this site
- Installability Criteria: what makes Chrome, Edge and Safari offer installation
- Installation by Platform: step-by-step install flows and labels
- App Identity & Updates: app IDs and the Chrome 144 update model
- Window Controls Overlay: custom title bars
- Protocol Handlers & Launch Handling: OS registration and link capturing in depth
- Publishing to App Stores: Microsoft Store, Google Play and more
- Isolated Web Apps: Chromium's packaged, high-trust apps
- Platform Support: the feature matrix for desktop and mobile browsers
External references
- Use Progressive Web Apps in Microsoft Edge (Microsoft Learn)
- Publish a PWA to the Microsoft Store (Microsoft Learn)
- Automatically start PWAs on OS login (Chrome for Developers)
- WebKit features in Safari 17.0 (WebKit): web apps on Mac
- Use Safari web apps on Mac (Apple Support)
- Firefox 143 release notes and Web Apps in Firefox (Mozilla)
- Desktop Entry Specification (freedesktop.org)
- Electron release timelines and Tauri 2.0
-
ChromeOS routes app launches through its own intent system; see Protocol Handlers & Launch Handling. ↩