UX and Design for Progressive Web Apps¶
PWA design is the work of making a web app behave like installed software once it leaves the browser tab: it launches without a flash, responds to touch instantly, fits around notches and keyboards, navigates without a browser back button, follows the system's light or dark appearance, and stays usable for everyone, including people who zoom, use screen readers or switch off animation. Installation removes the browser's interface, and with it a set of features users took for granted, so design decisions that are optional for a website become requirements for an app. This section covers those decisions from principles down to CSS properties, platform quirks and production code.
Key takeaways
- In a standalone window you inherit the browser's responsibilities: back navigation, reload, sharing, showing where the user is, and signaling when a link leaves the app.
- App-like UX is built from platform primitives (
<dialog>,inputmode,autocomplete,color-scheme,env(safe-area-inset-*), dynamic viewport units, View Transitions), not from frameworks that reimplement native behavior. - The launch experience spans the manifest (
background_color,theme_color, icons), Apple's startup images and your app shell's first paint. All of them must agree on color, in light and dark mode. - Never trade accessibility for an app look: keep pinch zoom, respect text size and reduced motion, size touch targets to at least 24×24 CSS px (44–48 px preferred), and keep focus visible.
- Design for three families of platform conventions (iOS/iPadOS, Android, desktop) and detect the display mode to add app chrome only where the browser's is missing.
- Every pattern must still work as a website in a browser tab. Installation is an enhancement.
Principles of app-like web UX¶
Users don't judge an installed PWA against other websites. They judge it against the native apps next to it on their home screen, dock or taskbar. The principles below are what that comparison rewards. Each links to the page that turns it into code.
1. Respond to every input within a frame¶
Native apps acknowledge a tap before they've done anything with it: a pressed state, a ripple, a highlight. Web pages often wait for a network response before anything changes. The rule for app-like UX is that visual feedback is immediate and independent of the work behind it:
- Draw a pressed state on
:activefor every control, and remove the double-tap-zoom delay withtouch-action: manipulationwhere it matters. - Update the UI optimistically for actions that almost always succeed (marking as read, starring, reordering) and reconcile afterwards. Offline UX & Fallbacks covers optimistic UI and its failure handling.
- Keep the main thread free so input is processed quickly; Interaction to Next Paint (INP) measures exactly this. See Runtime Performance and Core Web Vitals.
2. Launch instantly and never flash¶
The first second after tapping the icon defines whether an app "feels native". Three layers contribute: the platform's launch surface (Android's generated splash screen, iOS startup images), the browser's initial canvas, and your first paint. A cached app shell makes the first paint fast; matching colors across all three layers makes the transition invisible. Splash Screens & Theming covers every platform's launch surface and the checklist for avoiding white flashes, including dark mode.
3. Fill the screen, and respect what's on it¶
Apps draw edge to edge: under translucent status bars, around camera cutouts, above home indicators and gesture bars, and around the on-screen keyboard. The web has precise tools for each (viewport-fit=cover, env(safe-area-inset-*), svh/lvh/dvh, interactive-widget, the visualViewport API and Chromium's VirtualKeyboard API), and each has per-browser behavior you need to know. App-Like UX Patterns covers them in depth.
4. Follow platform conventions instead of inventing new ones¶
Users carry muscle memory from their platform: where the back button is, how tabs work, what a long press does, what happens when they swipe down. A PWA that behaves like its host platform needs less explanation than a consistent-everywhere design that behaves like none of them. You don't need separate designs per platform, but you do need to honor a few platform-specific expectations (see Platform conventions below) and adapt layout to the device class (Responsive & Adaptive Design).
5. Keep the user in control¶
Some "app-like" techniques take control away from users: disabling zoom, blocking text selection everywhere, hijacking the back gesture, auto-playing motion, forcing a light theme. Native platforms don't allow apps to do most of these, and the web shouldn't either:
- Never set
user-scalable=noormaximum-scale=1. - Disable selection only on UI chrome, never on content.
- Let back close the topmost overlay, then navigate, then leave the app, in that order.
- Honor
prefers-reduced-motion,prefers-color-schemeand the user's text size.
6. Be honest about state¶
An installed app is expected to work on a train, in a basement and on a flaky conference Wi-Fi. That doesn't mean everything must work offline, but it does mean the UI must say what's happening: cached content with its age, queued actions with their status, errors with a way to retry. Skeleton screens show progress for data that's genuinely on its way; stale-while-revalidate content with a "last updated" note is often better than any loading state. Offline UX & Fallbacks and Offline-First Data & Sync cover this in detail.
7. Make motion meaningful¶
Transitions between screens tell users where they came from and where they are. The View Transitions API gives the web the shared-element and slide transitions native apps use, for single-page apps and, in supporting browsers, across full page navigations. Motion should explain spatial relationships and be brief, and it must switch off under prefers-reduced-motion. View Transitions covers the API and patterns.
8. Accessibility is part of "native quality"¶
Native UI toolkits give apps accessibility semantics, focus management and dynamic text sizing mostly for free. On the web you get them from semantic HTML and lose them the moment you build custom widgets from <div>s. An app that can't be used with a screen reader, a keyboard or at 200% zoom is not app-like, whatever it looks like. Accessibility covers the PWA-specific concerns: custom title bars, standalone windows without browser UI, gestures, focus after client-side navigation, and announcements for offline and sync state.
How standalone mode changes the design problem¶
The display mode decides how much browser interface surrounds your app. In browser mode, your PWA is a website in a tab and the browser provides navigation, the URL, sharing and reload. In standalone (the mode almost every PWA requests), all of that disappears:
| Browser feature | In a tab | Standalone on Android | Standalone on iOS/iPadOS | Standalone on desktop |
|---|---|---|---|---|
| Back navigation | Back button, gestures | System back gesture/button | None | Keyboard shortcut only |
| Reload | Button, pull-to-refresh | Pull-to-refresh (Chrome) | None | Keyboard shortcut only |
| Current URL | Address bar | None | None | Copy URL in the app menu |
| Share current page | Browser menu | None | None | Varies by browser version |
| Page title | Tab label | Recents entry | App switcher shows the app name | Window title |
| Security indicator | Padlock, URL | None | None | App menu, site info |
| Leaving your site | Obvious from the URL | Out-of-scope pages open in a custom tab | Out-of-scope pages open in an in-app Safari view | Out-of-scope pages show a URL bar or open in the browser |
The design consequences follow directly from the table:
- Add a back affordance on every non-root screen where the platform lacks one, and make modals and sheets close on Android's back gesture.
- Offer refresh wherever content can go stale, and connect it to your service worker update flow (Updating Service Workers).
- Offer share and copy-link actions, because the address bar is gone (Web Share API).
- Make titles meaningful, because
document.titleis what the window and task switcher show. - Mark external links, because nothing else tells users they're leaving the app.
Because the same code also runs in a browser tab, you detect the display mode and add app chrome only where it's missing:
flowchart TD
A["Page loads"] --> B{"display-mode: standalone,<br/>fullscreen or WCO?<br/>or navigator.standalone"}
B -- "no (browser tab)" --> C["Browser provides back, reload, URL, share<br/>Show install promotion where appropriate"]
B -- "yes (installed app)" --> D{"Platform provides back?"}
D -- "Android" --> E["Rely on system back<br/>Make overlays history-aware"]
D -- "iOS, desktop" --> F["Show in-app back button<br/>when there is history"]
E --> G["Add refresh, share, external-link hints<br/>Hide install promotion"]
F --> G /* App-only chrome: hidden in browser tabs, shown in app windows. */
.app-only { display: none; }
@media (display-mode: standalone), (display-mode: fullscreen),
(display-mode: window-controls-overlay) {
.app-only { display: revert; }
.install-promo { display: none; }
}
The display-mode media feature is supported in Chrome 42, Firefox 47 and Safari 13 (iOS 12.2). Safari also exposes the non-standard navigator.standalone (on iOS and iPadOS, and on macOS since Safari 17, where it is true in Dock web apps). Check it before the media query on Apple platforms: an iOS web app whose manifest says standalone matches display-mode: fullscreen, and one without a manifest reports browser. Display Modes has a complete detection module, and Detecting Installed Apps covers the related question of whether a user has installed your app at all.
Platform conventions you design against¶
A PWA runs on platforms with different, well-established conventions. Designing "for the web" means designing so each of them feels at home:
| Convention | iOS and iPadOS | Android | Desktop (Windows, macOS, Linux, ChromeOS) |
|---|---|---|---|
| Primary navigation | Tab bar at the bottom | Navigation bar at the bottom, or a rail/drawer | Sidebar, top tabs, menus |
| Back | In-app back button, top left | System back gesture or button | In-app back button, keyboard shortcuts |
| Title and status area | Status bar always visible; your content may run under it | Status bar tinted with the theme color | Title bar tinted with the theme color, or a custom one with Window Controls Overlay |
| Minimum touch target | 44 × 44 pt (Apple HIG) | 48 × 48 dp (Material Design) | Pointer precision; keyboard access matters more |
| UI font | San Francisco (system-ui) | Roboto or the device maker's font (system-ui) | Segoe UI, San Francisco, Cantarell and others (system-ui) |
| Launch surface | Startup image if provided | Generated splash screen | None |
| Dismissing overlays | Swipe down on sheets, close buttons | Back gesture, scrim tap | Esc, click outside |
| Refreshing | Pull to refresh in lists | Pull to refresh in lists | Toolbar button, keyboard shortcut |
A single responsive design can satisfy most of this: a bottom navigation bar that becomes a rail on wide screens, a top app bar with a back button that's hidden where the platform provides back, system-ui for text, and touch targets sized for fingers when (pointer: coarse) matches. Platform details beyond design, such as installation flows and API availability, live in Platform Support.
A design checklist for installed PWAs¶
Use this as a review checklist before shipping. Each item links to where it's explained.
Launch and theming
-
background_colormatches the app shell's first frame (Splash Screens & Theming) -
theme_colorin the manifest, plus light and dark<meta name="theme-color" media>values -
<meta name="color-scheme" content="light dark">and an inlinehtmlbackground, so no white flash in dark mode - iOS startup images generated for current devices, or a deliberate decision to go without
- A 512 × 512 maskable icon for Android's splash screen and launcher (Icons & Maskable Icons)
Layout
-
width=device-width, initial-scale=1, viewport-fit=cover, and no zoom restrictions (App-Like UX Patterns) - Fixed headers and tab bars padded with
env(safe-area-inset-*), checked in landscape - Full-height layouts use
dvh/svhwithvhfallbacks - The composer, toolbar or submit button stays reachable when the on-screen keyboard is open
- Layout adapts from phone to tablet to desktop window, including narrow desktop windows (Responsive & Adaptive Design)
Interaction
- Touch targets at least 24 × 24 CSS px, ideally 44–48 px
- Visible
:activeand:focus-visiblestates on every control - Hover-only affordances wrapped in
@media (hover: hover)and never the only way to reach a control - Scroll containers use
overscroll-behavior: contain; any disabled pull-to-refresh is replaced by a refresh control - Inputs use the right
type,inputmode,enterkeyhintandautocomplete, with 16 px text on iOS
Navigation
- Back affordance on non-root screens in standalone mode on iOS and desktop
- Dialogs, sheets and drawers close on Android back and Esc
- Deep links work on cold launch, with a way back to the app's root
- External links are marked and open outside the app window
- Screen changes announce themselves and move focus appropriately (Accessibility)
Resilience and motion
- Loading states only after a short delay; cached content shown with its age
- Offline and queued states visible and understandable (Offline UX & Fallbacks)
- Screen transitions respect
prefers-reduced-motion(View Transitions) - Everything above verified in a browser tab too, not only in the installed app
Pages in this section¶
-
App-Like UX Patterns
Bottom navigation and history-aware modals, touch targets and
touch-action, press states, overscroll and pull-to-refresh, safe areas,dvh/svh/lvh, the on-screen keyboard (interactive-widget,visualViewport, VirtualKeyboard API),inputmodeandautocomplete, selectiveuser-select, system fonts, skeletons, dark mode and standalone-only CSS. -
Splash Screens & Theming
Android's generated splash screen and how Chromium picks its icon, iOS
apple-touch-startup-imagewith per-device media queries and generators, desktop launch behavior,theme_colorversus<meta name="theme-color">, dark-mode theme colors, runtime theme changes, iOS status bar styles and avoiding launch flashes. -
View Transitions
Animated transitions between screens with the View Transitions API, for single-page apps and cross-document navigations: shared elements, direction-aware animations, performance and reduced motion.
-
Responsive & Adaptive Design
Layouts that work from phones to desktop windows: breakpoints versus container queries, adaptive navigation, input-aware design with pointer and hover media features, foldables and resizable app windows.
-
Accessibility
Accessible installed apps: semantics and focus in client-side navigation, standalone windows without browser UI, custom title bars, gestures and target sizes, zoom and text scaling, motion, and announcing offline and sync states.
Related pages elsewhere on the site¶
-
Display Modes
standalone,minimal-ui,fullscreen,window-controls-overlayandtabbed, thedisplay-modemedia feature and how each platform renders them. -
Window Controls Overlay
Custom title bars for desktop PWAs, draggable regions and the title bar geometry API.
-
App Shell Model
The cached UI skeleton that makes launches and navigations instant.
-
Offline UX & Fallbacks
Connectivity states, offline pages, optimistic UI, queued actions and freshness indicators.
Suggested reading order¶
If you're turning an existing site into an installable app, read the pages in the order the problems appear:
- App-Like UX Patterns first. The viewport, safe-area, navigation and keyboard fixes are the most visible difference between "website in a window" and "app".
- Splash Screens & Theming next, because launch is the first thing users see after installing, and the manifest colors you choose are hard to change for existing installs.
- Responsive & Adaptive Design when your app runs on tablets and in resizable desktop windows, where a phone layout stretched wide looks unfinished.
- View Transitions once navigation works, to add motion that explains it.
- Accessibility throughout. It's listed last only because it touches every other page; review it before each release.
For the broader rollout (installation prompts, service worker, offline support), follow Migrating an Existing Site, and use the Production Checklist before launch.
What this section doesn't cover¶
To keep each topic in one place, some design-adjacent subjects live elsewhere:
- Icons, maskable icons and their safe zones are on Icons & Maskable Icons. The splash screen page covers only how icons appear on launch surfaces.
- Install prompts and custom install UI are on Install Prompts & Custom UI.
- Offline and error states are on Offline UX & Fallbacks.
- Loading and responsiveness metrics are on Performance.
- Per-platform limitations (for example, which APIs Home Screen web apps on iOS can use) are on iOS & iPadOS, Android and Desktop Platforms.
Further reading¶
On this site
- App-Like UX Patterns
- Splash Screens & Theming
- View Transitions
- Responsive & Adaptive Design
- Accessibility
- Display Modes
- App Shell Model
External references