# orientation manifest member

> The orientation member asks for a default screen orientation for an installed web app window; Chrome on Android honours it, desktop browsers and Safari do not.

`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

- **Type**: string, one of the `OrientationLockType` values: `any`, `natural`, `landscape`,
  `landscape-primary`, `landscape-secondary`, `portrait`, `portrait-primary`, or
  `portrait-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.

:::observed
Chrome 155 on macOS 26 (English UI), DevTools > Application > Manifest: the **Presentation**
section shows an **Orientation** row with the declared value, and a manifest carrying
`"orientation": "sideways"` adds `unknown 'orientation' value ignored.` under **Errors and
warnings** while the row shows nothing. On Chrome 155 for Android 16, `chrome://webapks` lists
the same value in its **Orientation** row for each installed WebAPK, which is the value the
APK was minted with, not the one in the live manifest.
:::

## 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

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.

```json
{
  "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

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.

```js
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

`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.

```js
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

- [display manifest member](/reference/manifest/display/)
- [scope manifest member](/reference/manifest/scope/)
- [WebAPK: how Chrome installs PWAs on Android](/reference/installation/webapk/)
- [Web Application Manifest: orientation member](https://www.w3.org/TR/appmanifest/#orientation-member) (w3.org)
- [ScreenOrientation: lock() method](https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation/lock) (developer.mozilla.org)