Manifest · Manifest member
orientation manifest member
Published Updated
orientation asks the browser to lock an installed web app’s top-level windows to a default
screen orientation, such as portrait or landscape, for as long as the app runs in an
app-like display mode. It is a declarative counterpart to screen.orientation.lock(): the
manifest sets the starting lock once at launch, the API changes it at runtime.
Chrome 39 on Android, Samsung Internet 4.0, Android WebView 39, and Firefox 79 for Android
apply the member to installed apps (BCD html.manifest.orientation). Desktop Chrome 39 and
Edge 79 parse it but do not lock a desktop window, Safari on iOS and macOS ignore it, and
Firefox 157 on desktop installs nothing from the manifest. The lock only takes effect in
fullscreen, standalone, or minimal-ui; a page opened in a browser tab is not rotated by
its manifest.
Member
Section titled “Member”- Type: string, one of the
OrientationLockTypevalues:any,natural,landscape,landscape-primary,landscape-secondary,portrait,portrait-primary, orportrait-secondary. - Default: absent, which means no lock. The window follows the device’s sensor and the user’s rotation-lock setting, exactly as a browser tab does.
- Example value:
"portrait".
natural is the device’s own default (portrait on most phones, landscape on most tablets and
every laptop). portrait and landscape allow either of the two variants in that axis, so a
phone turned upside down stays readable; the -primary and -secondary forms pin a single
one. any is written when a manifest wants to say explicitly that it does not care, and is
treated the same as leaving the member out.
A value outside the list is dropped, not an error for the whole manifest: Chromium logs
unknown 'orientation' value ignored. and continues with no lock. The lock is a property of
the app window, so it applies to every in-scope document the window shows and not to the
same URLs opened in a browser tab.
Examples
Section titled “Examples”The declaration is one line; the interesting code is what the page does when the lock is not honoured.
Declaring a portrait-only field journal
Section titled “Declaring a portrait-only field journal”A data-entry app whose forms are laid out for a phone held upright locks to portrait. On
Chrome for Android the installed app ignores the device rotating; in a browser tab, on desktop,
and on iOS the same manifest has no effect and the layout must still work in landscape.
{ "name": "Field Journal", "short_name": "Journal", "start_url": "/", "display": "standalone", "orientation": "portrait"}portrait rather than portrait-primary is the better choice for a phone app: it lets the
screen flip 180 degrees when the user turns the device around, which portrait-primary would
block.
Reading the current orientation and reacting to changes
Section titled “Reading the current orientation and reacting to changes”The manifest lock is invisible to script, but the orientation it produces is not. The Screen
Orientation API reports it through screen.orientation.type; browsers without the API (Safari
on iOS before 16.4) take the fallback branch and are read through a media query, which is
also what fires on every rotation in both branches.
function currentOrientation() { if ('orientation' in screen && screen.orientation?.type) { return screen.orientation.type; // e.g. "portrait-primary" } // Fallback: no Screen Orientation API; derive the axis from the viewport. return matchMedia('(orientation: portrait)').matches ? 'portrait' : 'landscape';}
const query = matchMedia('(orientation: portrait)');query.addEventListener('change', () => { document.documentElement.dataset.orientation = currentOrientation();});document.documentElement.dataset.orientation = currentOrientation();Because the manifest lock holds the app in one axis, the change listener fires only in the
environments that did not honour the lock, which makes it the natural place to switch to a
two-column layout.
Locking at runtime where the manifest is ignored
Section titled “Locking at runtime where the manifest is ignored”screen.orientation.lock() can impose the same lock from script, with two constraints the
manifest does not have: on Android the document must be in fullscreen first, and desktop
browsers reject the call with NotSupportedError (MDN, ScreenOrientation.lock()). The
fallback is to do nothing and rely on the responsive layout.
async function lockPortraitIfPossible() { if (!('orientation' in screen) || typeof screen.orientation.lock !== 'function') { return 'unsupported'; // Safari: no lock(); layout stays responsive } try { await document.documentElement.requestFullscreen(); await screen.orientation.lock('portrait'); return 'locked'; } catch (err) { // Desktop Chrome and Edge reject as not supported; a sandboxed iframe rejects as a security error. return err.name; }}Call it from a click handler: both requestFullscreen() and the lock require user activation,
and the returned string tells the UI whether to keep its rotate-the-device hint.
See also
Section titled “See also”- display manifest member
- scope manifest member
- WebAPK: how Chrome installs PWAs on Android
- Web Application Manifest: orientation member (w3.org)
- ScreenOrientation: lock() method (developer.mozilla.org)
Specifications
| Specification | Status |
|---|---|
| Web App Manifest: orientation | W3C |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Desktop) | Yes | 39 | high | source | — |
| Chrome (Android) | Yes | 39 | high | source | 1 |
| Edge (Desktop) | Yes | 79 | high | source | 2 |
| Firefox (Desktop) | No | — | high | source | 3 |
| Firefox (Android) | Yes | 79 | high | source | — |
| Safari (macOS) | No | — | high | source | 4 |
| Safari (iOS) | No | — | high | source | 56 |
| Samsung Internet | Yes | 4.0 | high | source | 7 |
| WebView (Android) | Yes | 39 | high | source | 8 |
- Derived by browser-compat-data mirroring from Chrome.
- Derived by browser-compat-data mirroring from Chrome.
- No Firefox support is recorded in browser-compat-data.
- No Safari support is recorded in browser-compat-data.
- No Safari on iOS support is recorded in browser-compat-data.
- Derived by browser-compat-data mirroring from Safari.
- Derived by browser-compat-data mirroring from Chrome Android.
- Derived by browser-compat-data mirroring from Chrome Android.