@capgo/capacitor-screen-orientation
v8.4.3
Published
Screen orientation plugin with support for bypassing orientation lock
Readme
@capgo/capacitor-screen-orientation
🤖 Building with an AI coding agent? Install Capgo skills so Claude Code, Codex, Cursor, Copilot, or Gemini CLI know how to install and use
@capgo/capacitor-screen-orientationin your Capacitor app:npx skills add Cap-go/capgo-skills. Then ask your agent: "Add @capgo/capacitor-screen-orientation to my Capacitor app and wire it up." Agent setup guides · Hosted MCP
Screen orientation plugin with support for detecting true physical device orientation
Features
- 📱 Full Screen Orientation API support
- 🔍 Detect true physical device orientation using motion sensors (even when orientation lock is enabled)
- iOS: Uses Core Motion framework
- Android: Uses accelerometer sensor
- 🔒 Detect if orientation lock is enabled by comparing physical vs UI orientation
- 🔄 Real-time orientation change detection
- 🎯 Lock orientation to specific modes
- 📖 Foldable devices (Galaxy Z Fold/Flip, Pixel Fold, Surface Duo, iPhone Duo): posture, hinge, size classes, and live fold updates
- 🌐 Web platform support
Important Note: This plugin can detect the physical orientation of the device using motion sensors, but it cannot bypass the UI orientation lock. The screen will still respect the user's orientation lock setting. This is useful for knowing how the device is physically held vs. how the UI is displayed.
Foldable devices
Foldables give you more than a wider canvas: the hinge, cover screen, and posture change how people hold the phone. This plugin reports that state in JavaScript so you can adapt layouts, controls, and media without guessing from window size alone. It targets Galaxy Z Fold and Flip, Pixel Fold, Surface Duo, and iPhone Duo (iOS 27.1+), alongside the orientation APIs already in this package.
What you get
- Device and posture:
isDeviceFoldable(),getFoldState()(flat/half-opened,postureflat/tabletop/book, hinge bounds, separating fold, occluded hinge area when applicable) - Hinge angle:
getHingeAngle()andhingeAngleChange(native sensor on Android; iPhone Duo on iOS 27.1+;nullon web) - Layout signals:
getSizeClass()andsizeClassChange(Apple compact/regular plus Material width/height classes) - iPhone Duo extras:
activeDisplay(inner/outer),getReservedRegions(),getBarPlacement(),setVerticalBarBehavior() - Live updates:
foldStateChangewhen posture or hinge geometry changes the reported fold state (rotation alone may not fire ifdevicePostureis available but usable viewport segments are not)
Good fits
- Two-pane layouts that follow the hinge instead of a fixed breakpoint
- Toolbar, gamepad, or reader chrome that moves when the user folds to tabletop or book
- Cover-screen vs inner-screen flows on foldables and iPhone Duo
- Size-class driven UI that reacts when the user opens the device fully
Quick start
import { ScreenOrientation } from '@capgo/capacitor-screen-orientation';
const fold = await ScreenOrientation.getFoldState();
console.log(fold.posture, fold.hingeBounds);
await ScreenOrientation.addListener('foldStateChange', (state) => {
document.documentElement.dataset.posture = state.posture;
});iPhone Duo (iOS 27.1+)
| Pose | posture | Hinge (typical) |
| --- | --- | --- |
| Open flat, or closed on the outer display | flat | 180 open, 0 closed |
| Held half-open like a book | book | vertical hinge, about 20 to 160 |
| Propped half-open like a laptop | tabletop | horizontal hinge |
Non-folding iPhones and iOS before 27.1 stay flat with no hinge. Android uses Jetpack WindowManager; web uses the Device Posture API and viewport segments when the browser exposes them (hinge angle stays null on web).
Fold API (full types below): isDeviceFoldable(), getFoldState(), getHingeAngle(), getReservedRegions(), getBarPlacement(), setVerticalBarBehavior(), getSizeClass(), listeners foldStateChange, hingeAngleChange, sizeClassChange.
Documentation
The most complete doc is available here: https://capgo.app/docs/plugins/screen-orientation/
Compatibility
| Plugin version | Capacitor compatibility | Maintained | | -------------- | ----------------------- | ---------- | | v8.*.* | v8.*.* | ✅ | | v7.*.* | v7.*.* | On demand | | v6.*.* | v6.*.* | ❌ | | v5.*.* | v5.*.* | ❌ |
Note: The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.
Install
You can use our AI-Assisted Setup to install the plugin. Add the Capgo skills to your AI tool using the following command:
npx skills add https://github.com/cap-go/capacitor-skills --skill capacitor-pluginsThen use the following prompt:
Use the `capacitor-plugins` skill from `cap-go/capacitor-skills` to install the `@capgo/capacitor-screen-orientation` plugin in my project.If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:
npm install @capgo/capacitor-screen-orientation
npx cap synciOS Configuration
To detect physical device orientation using motion sensors on iOS, you need to add the Motion Usage Description to your Info.plist:
<key>NSMotionUsageDescription</key>
<string>This app uses motion sensors to detect the true physical orientation of your device.</string>Locking the screen orientation only applies to the Capacitor view controller by default. To also apply the lock to presented view controllers (for example, ones shown by the Browser plugin), add this to your app's AppDelegate.swift:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask(rawValue: (self.window!.rootViewController as! CAPBridgeViewController).supportedInterfaceOrientations.rawValue)
}Make sure UISupportedInterfaceOrientations in Info.plist includes every orientation you want to lock to (for example landscape left/right when locking to landscape).
Usage
import { ScreenOrientation } from '@capgo/capacitor-screen-orientation';
// Get current orientation
const result = await ScreenOrientation.orientation();
console.log('Current orientation:', result.type);
// Lock to landscape
await ScreenOrientation.lock({ orientation: 'landscape' });
// Lock UI orientation and track physical orientation via motion sensors
// Note: UI will still respect user's orientation lock, but you'll get physical orientation events
await ScreenOrientation.lock({
orientation: 'portrait',
bypassOrientationLock: true
});
// Listen for orientation changes
const listener = await ScreenOrientation.addListener(
'screenOrientationChange',
(result) => {
console.log('Orientation changed:', result.type);
}
);
// Unlock orientation
await ScreenOrientation.unlock();
// Start motion-based tracking to detect physical device orientation
// This uses motion sensors to track how the device is actually held
await ScreenOrientation.startOrientationTracking({
bypassOrientationLock: true
});
// Check if device orientation lock is enabled
// Compares physical device orientation (from sensors) with UI orientation (from system)
const lockStatus = await ScreenOrientation.isOrientationLocked();
if (lockStatus.locked) {
console.log('Orientation lock is ON!');
console.log('Physical device orientation:', lockStatus.physicalOrientation);
console.log('UI orientation:', lockStatus.uiOrientation);
} else {
console.log('Orientation lock is OFF or motion tracking not active');
}
// Stop motion-based tracking
await ScreenOrientation.stopOrientationTracking();
// Remove listener
await listener.remove();
// iPhone Duo and Android foldables. Flat on phones that do not fold.
const { posture, activeDisplay } = await ScreenOrientation.getFoldState();
const { angle, status } = await ScreenOrientation.getHingeAngle();
const { verticalBarEdge, inset } = await ScreenOrientation.getBarPlacement();
const { widthClass } = await ScreenOrientation.getSizeClass();API
orientation()lock(...)unlock()startOrientationTracking(...)stopOrientationTracking()isOrientationLocked()isDeviceFoldable()getFoldState()getHingeAngle()getReservedRegions()getBarPlacement()setVerticalBarBehavior(...)getSizeClass()addListener('screenOrientationChange', ...)addListener('foldStateChange', ...)addListener('hingeAngleChange', ...)addListener('sizeClassChange', ...)removeAllListeners()getPluginVersion()- Interfaces
- Type Aliases
Capacitor Screen Orientation Plugin interface.
Provides methods to detect and control screen orientation, with support for detecting true physical device orientation using motion sensors.
orientation()
orientation() => Promise<ScreenOrientationResult>Get the current screen orientation.
Returns the current orientation of the device screen.
Returns: Promise<ScreenOrientationResult>
Since: 1.0.0
lock(...)
lock(options: OrientationLockOptions) => Promise<void>Lock the screen orientation to a specific type.
Locks the screen to the specified orientation. On iOS, if bypassOrientationLock is true, it will also start tracking physical device orientation using motion sensors.
Note: The UI will still respect the user's orientation lock setting. Motion tracking allows you to detect how the device is physically held even when the UI doesn't rotate.
On iOS 15 there is no public API to force a rotation, so the lock restricts the allowed orientations and the UI rotates into them when the device orientation allows it. On iOS 16 and later the rotation is requested immediately.
| Param | Type | Description |
| ------------- | ------------------------------------------------------------------------- | ------------------------------------ |
| options | OrientationLockOptions | Options for locking the orientation. |
Since: 1.0.0
unlock()
unlock() => Promise<void>Unlock the screen orientation.
Allows the screen to rotate freely based on device position. Also stops any motion-based orientation tracking if it was enabled.
Since: 1.0.0
startOrientationTracking(...)
startOrientationTracking(options?: StartOrientationTrackingOptions | undefined) => Promise<void>Start tracking device orientation using motion sensors.
This method is useful when you want to track the device's physical orientation independently from the screen orientation lock. It uses Core Motion on iOS to detect orientation changes.
| Param | Type | Description |
| ------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------ |
| options | StartOrientationTrackingOptions | Options for starting orientation tracking. |
Since: 1.0.0
stopOrientationTracking()
stopOrientationTracking() => Promise<void>Stop tracking device orientation using motion sensors.
Stops the motion-based orientation tracking if it was started.
Since: 1.0.0
isOrientationLocked()
isOrientationLocked() => Promise<OrientationLockStatusResult>Check if device orientation lock is currently enabled.
This method compares the physical device orientation (from motion sensors) with the UI orientation. If they differ, orientation lock is enabled.
Note: This requires motion tracking to be active via startOrientationTracking() or lock() with bypassOrientationLock: true. Works on both iOS (Core Motion) and Android (Accelerometer).
Returns: Promise<OrientationLockStatusResult>
Since: 1.0.0
isDeviceFoldable()
isDeviceFoldable() => Promise<DeviceFoldableResult>Whether this device folds, and whether it can stand half-open like a laptop.
On web, both flags are false when the browser lacks fold APIs or the current reading shows
no fold (devicePosture.type is continuous with a single viewport segment). foldable
becomes true when posture is folded or the viewport splits into two segments.
On web, supportsTabletop reflects the current reading (for example, false in a book
pose) rather than permanent device capability.
On Android, both flags are false on phones that do not fold. On iOS, both are false
except on iPhone Duo (iOS 27.1 or later).
Returns: Promise<DeviceFoldableResult>
Since: 8.2.0
getFoldState()
getFoldState() => Promise<FoldState>Read the current fold.
Resolves to a flat state when the device has no fold. On web, uses
navigator.devicePosture and window.viewport.segments when the browser provides them.
Returns: Promise<FoldState>
Since: 8.2.0
getHingeAngle()
getHingeAngle() => Promise<HingeAngleResult>Read the hinge angle in degrees.
0 is closed and 180 is flat. angle is null without a hinge sensor.
Returns: Promise<HingeAngleResult>
Since: 8.2.0
getReservedRegions()
getReservedRegions() => Promise<{ regions: ReservedRegion[]; }>Read the fold and anything covering the screen.
iPhone Duo reports the fold, the vertical status bar area, and the under-display camera. Resolves to an empty list on Android, web, and iOS before 27.1.
Returns: Promise<{ regions: ReservedRegion[]; }>
Since: 8.2.0
getBarPlacement()
getBarPlacement() => Promise<BarPlacement>Read where iPhone Duo puts native tab bars and toolbars.
verticalBarEdge is null when bars stay horizontal, and always on Android and web.
Returns: Promise<BarPlacement>
Since: 8.2.0
setVerticalBarBehavior(...)
setVerticalBarBehavior(options: { behavior: 'automatic' | 'disabled'; }) => Promise<{ applied: boolean; }>Choose whether iPhone Duo may move this app's bars to the side.
Resolves to { applied: false } unless the app's bridge view controller opts in.
Android and web always resolve to { applied: false }.
| Param | Type | Description |
| ------------- | ----------------------------------------------------- | ------------------------------------------------------------------------ |
| options | { behavior: 'automatic' | 'disabled'; } | automatic lets the system move bars. disabled keeps them horizontal. |
Returns: Promise<{ applied: boolean; }>
Since: 8.2.0
getSizeClass()
getSizeClass() => Promise<SizeClass>Read the window size classes.
Returns: Promise<SizeClass>
Since: 8.2.0
addListener('screenOrientationChange', ...)
addListener(eventName: 'screenOrientationChange', listenerFunc: (result: ScreenOrientationResult) => void) => Promise<PluginListenerHandle>| Param | Type |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| eventName | 'screenOrientationChange' |
| listenerFunc | (result: ScreenOrientationResult) => void |
Returns: Promise<PluginListenerHandle>
addListener('foldStateChange', ...)
addListener(eventName: 'foldStateChange', listenerFunc: (state: FoldState) => void) => Promise<PluginListenerHandle>Listen for fold changes.
On a foldable this also fires when the device rotates, because the hinge bounds rotate with the window.
| Param | Type | Description |
| ------------------ | ------------------------------------------------------------------- | ------------------------------------------ |
| eventName | 'foldStateChange' | The event name. Must be 'foldStateChange'. |
| listenerFunc | (state: FoldState) => void | Callback invoked with the new fold state. |
Returns: Promise<PluginListenerHandle>
Since: 8.2.0
addListener('hingeAngleChange', ...)
addListener(eventName: 'hingeAngleChange', listenerFunc: (event: HingeAngleResult) => void) => Promise<PluginListenerHandle>Listen for hinge angle changes.
On Android the hinge sensor runs only while at least one listener is registered.
On iOS this fires on iPhone Duo (iOS 27.1 or later). On web, hinge angle stays null;
use foldStateChange for posture updates from the Device Posture API.
| Param | Type | Description |
| ------------------ | --------------------------------------------------------------------------------- | ------------------------------------------- |
| eventName | 'hingeAngleChange' | The event name. Must be 'hingeAngleChange'. |
| listenerFunc | (event: HingeAngleResult) => void | Callback invoked with the angle in degrees. |
Returns: Promise<PluginListenerHandle>
Since: 8.2.0
addListener('sizeClassChange', ...)
addListener(eventName: 'sizeClassChange', listenerFunc: (sizeClass: SizeClass) => void) => Promise<PluginListenerHandle>Listen for size class changes, such as unfolding, rotating, or resizing.
| Param | Type | Description |
| ------------------ | ----------------------------------------------------------------------- | ------------------------------------------ |
| eventName | 'sizeClassChange' | The event name. Must be 'sizeClassChange'. |
| listenerFunc | (sizeClass: SizeClass) => void | Callback invoked with the new size class. |
Returns: Promise<PluginListenerHandle>
Since: 8.2.0
removeAllListeners()
removeAllListeners() => Promise<void>Remove all listeners for this plugin.
Removes all registered event listeners.
Since: 1.0.0
getPluginVersion()
getPluginVersion() => Promise<{ version: string; }>Get the native plugin version.
Returns the current version of the native plugin implementation.
Returns: Promise<{ version: string; }>
Since: 1.0.0
Interfaces
ScreenOrientationResult
Result returned by the orientation() method.
| Prop | Type | Description | Since |
| ---------- | ----------------------------------------------------------- | ----------------------------- | ----- |
| type | OrientationType | The current orientation type. | 1.0.0 |
OrientationLockOptions
Options for locking the screen orientation.
| Prop | Type | Description | Default | Since |
| --------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
| orientation | OrientationLockType | The orientation type to lock to. | | 1.0.0 |
| bypassOrientationLock | boolean | Whether to track physical device orientation using motion sensors. When true, uses device motion sensors to detect the true physical orientation of the device, even when the device orientation lock is enabled. Important: This does NOT bypass the UI orientation lock. The screen will still respect the user's orientation lock setting. This option only affects orientation detection/tracking - you'll receive orientation change events based on how the device is physically held, but the UI will not rotate if orientation lock is enabled. Supported on iOS (Core Motion) and Android (Accelerometer). | false | 1.0.0 |
StartOrientationTrackingOptions
Options for starting orientation tracking using motion sensors.
| Prop | Type | Description | Default | Since |
| --------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
| bypassOrientationLock | boolean | Whether to track physical device orientation using motion sensors. When true, uses device motion sensors to detect the true physical orientation of the device, even when the device orientation lock is enabled. Important: This does NOT bypass the UI orientation lock. This only enables detection of the physical orientation. Supported on iOS (Core Motion) and Android (Accelerometer). | false | 1.0.0 |
OrientationLockStatusResult
Result returned by the isOrientationLocked() method.
| Prop | Type | Description | Since |
| ------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| locked | boolean | Whether the device orientation lock is currently enabled. This is determined by comparing the physical device orientation (from motion sensors) with the UI orientation. If they differ, orientation lock is enabled. Available on iOS (Core Motion) and Android (Accelerometer) when motion tracking is active. | 1.0.0 |
| physicalOrientation | OrientationType | The physical orientation of the device from motion sensors. Available when motion tracking is active (iOS and Android). | 1.0.0 |
| uiOrientation | OrientationType | The current UI orientation reported by the system. | 1.0.0 |
DeviceFoldableResult
Whether this device can fold.
| Prop | Type | Description | Since |
| ---------------------- | -------------------- | --------------------------------------------- | ----- |
| foldable | boolean | Whether the device has a fold. | 8.2.0 |
| supportsTabletop | boolean | Whether it can stand half-open like a laptop. | 8.2.0 |
FoldState
Current fold of the window.
Phones that do not fold, and iOS before 27.1, resolve to a flat state.
iPhone Duo reports book, tabletop, or flat, and which display is in front.
| Prop | Type | Description | Since |
| ---------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ----- |
| state | FoldStateValue | Posture of the fold. | 8.2.0 |
| isSeparating | boolean | Whether the fold splits the web view into two areas. | 8.2.0 |
| posture | FoldPosture | How the device is held. | 8.2.0 |
| hingeOrientation | HingeOrientation | Direction of the hinge. Omitted when there is no fold. | 8.2.0 |
| hingeBounds | FoldBounds | Position of the fold in CSS pixels. Omitted when there is no fold. | 8.2.0 |
| occludedBounds | FoldBounds | Area the hinge covers. Present when the hinge has a physical gap. | 8.2.0 |
| activeDisplay | 'inner' | 'outer' | Which display is showing the app on iPhone Duo. Omitted on Android, web, and iOS before 27.1. | 8.2.0 |
| hingeMargins | { top: number; right: number; bottom: number; left: number; } | Space the system keeps clear around the fold, in CSS pixels. iPhone Duo reports 20 on each side of a vertical fold. | 8.2.0 |
| cameraBounds | FoldBounds[] | Areas covering the display, such as the iPhone Duo camera. Omitted when there are none. | 8.2.0 |
FoldBounds
A rectangle in CSS pixels, relative to the web view.
| Prop | Type |
| ------------ | ------------------- |
| x | number |
| y | number |
| width | number |
| height | number |
HingeAngleResult
Angle between the two halves of a foldable, in degrees.
0 is closed and 180 is flat. angle is null when the device has no hinge sensor.
| Prop | Type | Description | Since |
| ------------ | --------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----- |
| angle | number | null | Hinge angle in degrees, or null when no sensor is available. | 8.2.0 |
| status | 'closed' | 'partiallyOpen' | 'fullyOpen' | null | How far open iPhone Duo is, as the system sees it. null on Android and web. | 8.2.0 |
ReservedRegion
A region the system reserves on iPhone Duo.
division is the fold. occlusion is something covering the display, such as the camera.
| Prop | Type | Description | Since |
| -------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----- |
| kind | 'division' | 'occlusion' | What the region is. | 8.2.0 |
| isActive | boolean | Whether the region applies right now. An inactive division is a flat fold. | 8.2.0 |
| x | number | Left edge, in CSS pixels. | 8.2.0 |
| y | number | Top edge, in CSS pixels. | 8.2.0 |
| width | number | Width in CSS pixels. | 8.2.0 |
| height | number | Height in CSS pixels. | 8.2.0 |
| margins | { top: number; right: number; bottom: number; left: number; } | Space to keep clear around the region, in CSS pixels. | 8.2.0 |
BarPlacement
Where iPhone Duo places native bars.
| Prop | Type | Description | Since |
| --------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------- | ----- |
| verticalBarEdge | 'leading' | 'trailing' | null | The edge the system moves tab bars and toolbars to. null when they stay horizontal. | 8.2.0 |
| inset | number | How much room the vertical bar takes, in points. 0 when bars stay horizontal. | 8.2.0 |
SizeClass
Window size classes.
horizontal and vertical follow Apple's compact/regular split.
On Android and web, regular starts at 600 CSS pixels wide and 480 tall.
widthClass and heightClass follow Material window size classes.
| Prop | Type | Description | Since |
| ----------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| horizontal | 'compact' | 'regular' | Width size class. compact on a phone, regular on the inner display of a foldable, a tablet, or a window at least 600 CSS pixels wide. | 8.2.0 |
| vertical | 'compact' | 'regular' | Height size class. regular starts at 480 CSS pixels. | 8.2.0 |
| widthClass | 'compact' | 'medium' | 'expanded' | 'large' | 'extraLarge' | Material window width class. | 8.2.0 |
| heightClass | 'compact' | 'medium' | 'expanded' | Material window height class. | 8.2.0 |
PluginListenerHandle
| Prop | Type |
| ------------ | ----------------------------------------- |
| remove | () => Promise<void> |
Type Aliases
OrientationType
Orientation type that describes the orientation state of the device.
'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'
OrientationLockType
Orientation lock type that can be used to lock the device orientation.
'any' | 'natural' | 'landscape' | 'portrait' | 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'
FoldStateValue
How open a foldable is.
closed is reserved. A device shut onto its cover display reports flat.
'flat' | 'half-opened'
FoldPosture
How a half-open foldable is held.
tabletop is a horizontal hinge, like a laptop. book is a vertical hinge.
'flat' | 'tabletop' | 'book'
HingeOrientation
Direction of the hinge relative to the window.
'horizontal' | 'vertical'
Foldable support adapted from erkamyaman/capacitor-foldable (MIT, Copyright (c) Erkam Yaman).
