Size classes, foldable hinges, reserved regions, and window segments
Phones, tablets, split screens, and foldables give an app windows of very different shapes. These hooks tell a layout what it is working with, in the terms each platform uses:
compact or regular in each axis.
Branch on this rather than raw width.All of them are named imports from one, and the readings are already
current on the first render.
import { useSizeClass } from 'one'
function Inbox() { const { horizontal } = useSizeClass() return horizontal === 'regular' ? <SplitInbox /> : <StackedInbox />}useSizeClass re-renders when the window changes class, for example when an
iPad app enters split view or a foldable opens.
Size class and hinge come from a Nitro module, OneAdaptive. When one is
imported it reads both synchronously from native code, so the first render
never shows a default and then flips. The first component that subscribes
starts the native monitor; when the last one unmounts the monitor stops, and
the next subscriber reads a fresh value.
horizontalSizeClass and
verticalSizeClass traits and re-reads them when the trait collection
changes (rotation, split view, Stage Manager). The hinge comes from
UIHingeInteraction on iOS 27.1 and later.WindowMetrics: width below 600dp is compact, height below
480dp is compact, anything else is regular. It re-reads on configuration
and window metrics changes. The hinge posture comes from Jetpack
WindowManager’s FoldingFeature (FLAT is fully open, HALF_OPENED is
partially open) and the angle from the hinge sensor.Reserved regions come from a native view, One.UI.ReservedRegions.Provider,
that reports the regions inside its own bounds. On iOS 27.1 they are UIKit’s
reservedRegions and update as the hinge moves. On Android a fold from
Jetpack WindowManager is a division, active while it separates or fully
covers the display and inactive while the device lies flat, and each display
cutout is an occlusion. Window segments are computed in
JavaScript: each active division that spans the provider edge to edge cuts it
in two.
| Hooks and readers | Type | Description |
|---|---|---|
useSizeClass() => SizeClassThe window size class, live. | () => SizeClass | The window size class, live. |
getSizeClass() => Promise<SizeClass>Read the size class once. | () => Promise<SizeClass> | Read the size class once. |
useHinge() => HingeState | nullThe hinge, live. null on a device without one. | () => HingeState | null | The hinge, live. null on a device without one. |
getHinge() => Promise<HingeState | null>Read the hinge once. | () => Promise<HingeState | null> | Read the hinge once. |
onHingeChange(callback: (hinge: HingeState | null) => void) => () => voidSubscribe outside React. Returns the unsubscribe function. | (callback: (hinge: HingeState | null) => void) => () => void | Subscribe outside React. Returns the unsubscribe function. |
useReservedRegions(options?: ReservedRegionOptions) => readonly ReservedRegion[]Regions in the nearest provider. Active ones only unless includeInactive is set. | (options?: ReservedRegionOptions) => readonly ReservedRegion[] | Regions in the nearest provider. Active ones only unless includeInactive is set. |
useReservedRegionsReady() => booleanTrue once the provider has its first native reading, even an empty one. | () => boolean | True once the provider has its first native reading, even an empty one. |
useWindowSegments() => readonly WindowSegment[]The free parts of the provider. Empty until ready. | () => readonly WindowSegment[] | The free parts of the provider. Empty until ready. |
useSpanning() => booleanWhether an active division splits the provider. | () => boolean | Whether an active division splits the provider. |
| SizeClass | Type | Description |
|---|---|---|
horizontalUserInterfaceSizeClasscompact, regular, or unspecified. | UserInterfaceSizeClass | compact, regular, or unspecified. |
verticalUserInterfaceSizeClasscompact, regular, or unspecified. | UserInterfaceSizeClass | compact, regular, or unspecified. |
| HingeState | Type | Description |
|---|---|---|
statusHingeStatusunknown, closed, partiallyOpen, or fullyOpen. | HingeStatus | unknown, closed, partiallyOpen, or fullyOpen. |
anglenumberThe hinge angle in radians. | number | The hinge angle in radians. |
| ReservedRegion | Type | Description |
|---|---|---|
idstringStable for the life of the region. | string | Stable for the life of the region. |
kindReservedRegionKinddivision: a fold not to straddle. occlusion: a cutout or control that hides content. | ReservedRegionKind | division: a fold not to straddle. occlusion: a cutout or control that hides content. |
frame{ x: number; y: number; width: number; height: number }The region including margins, in the provider's points. | { x: number; y: number; width: number; height: number } | The region including margins, in the provider's points. |
margins{ top: number; left: number; bottom: number; right: number }Room kept around the region for interactive content. | { top: number; left: number; bottom: number; right: number } | Room kept around the region for interactive content. |
isActivebooleanWhether the region applies right now. | boolean | Whether the region applies right now. |
| ReservedRegionOptions | Type | Description |
|---|---|---|
kindReservedRegionKindReturn only divisions or only occlusions. | ReservedRegionKind | Return only divisions or only occlusions. |
includeInactivebooleanAlso return regions that do not apply right now. | boolean | Also return regions that do not apply right now. |
| iOS | Android | Web | |
|---|---|---|---|
| Size class | Scene traits | Width and height breakpoints | Always regular |
| Hinge | iOS 27.1 and later | Fold posture and hinge sensor | Always null |
| Reserved regions | iOS 27.1 and later | Folds and cutouts | None |
| Region margins | Reported | Always 0 | None |
unspecified is a real value. Treat it like compact unless you know
better, so an unusual window gets the narrow layout.FoldingFeature, the status is closed only for an
official 0° sensor reading and unknown otherwise, with the sensor angle
kept. A device with no hinge at all reads null.onHingeChange. Call the function it returns when the owner
unmounts. The hooks clean up on their own.import { View } from 'react-native'import { One, useWindowSegments, type WindowSegment } from 'one'
function Board() { const segments = useWindowSegments() if (segments.length < 2) return <Canvas /> const [first, second] = segments return ( <View style={{ flex: 1 }}> <View style={place(first)}><Canvas /></View> <View style={place(second)}><Palette /></View> </View> )}
function place({ x, y, width, height }: WindowSegment) { return { position: 'absolute', left: x, top: y, width, height } as const}
export default function Screen() { return ( <One.UI.ReservedRegions.Provider style={{ flex: 1 }}> <Board /> </One.UI.ReservedRegions.Provider> )}Each segment is a rect in the provider’s coordinates, so it maps straight to an absolutely positioned view. When the device is not split, there is one segment and the canvas fills the screen.
import { onHingeChange } from 'one'
const stop = onHingeChange((hinge) => { if (hinge?.status === 'partiallyOpen') pauseVideo()})The callback gets null on a device without a hinge, so check before reading
status.
One.iOS.ArrangementView hosts SwiftUI’s ArrangementView on iOS 27.1 and
later, and throws on earlier versions. It places a primary and a secondary
pane side by side (split), one over the other (overlay), or lets SwiftUI
choose for the window (automatic). On web it renders the two panes in a
row.
import { One } from 'one'
function Workspace() { return ( <One.iOS.ArrangementView arrangementViewStyle="split" splitArrangementLayoutRatio={0.5} primary={<Chat />} secondary={<Preview />} /> )}Each pane can also be a child, ArrangementView.Primary and
ArrangementView.Secondary, which take the same split and overlay props to
size that pane alone. Leading and Detail are aliases for them.
| One.iOS.ArrangementView | Type | Description |
|---|---|---|
primaryReactNodeThe first pane. | ReactNode | The first pane. |
secondaryReactNodeThe second pane. | ReactNode | The second pane. |
leadingReactNodeAlias of primary. | ReactNode | Alias of primary. |
detailReactNodeAlias of secondary. | ReactNode | Alias of secondary. |
childrenReactNodePrimary and Secondary panes, as an alternative to the props. | ReactNode | Primary and Secondary panes, as an alternative to the props. |
arrangementViewStyleArrangementViewStyle = "automatic"automatic, split, or overlay, optionally with the axes it applies to. | ArrangementViewStyle = "automatic" | automatic, split, or overlay, optionally with the axes it applies to. |
splitArrangementLayoutRatioSplitLayoutRatioThe share of space for the split panes, as a number or per-axis bounds. | SplitLayoutRatio | The share of space for the split panes, as a number or per-axis bounds. |
splitArrangementLayoutSizeSplitLayoutSizeMinimum, ideal, and maximum pane sizes in points. | SplitLayoutSize | Minimum, ideal, and maximum pane sizes in points. |
splitArrangementFixedLayoutSizeSplitFixedLayoutSizeKeep the split size fixed, per axis. | SplitFixedLayoutSize | Keep the split size fixed, per axis. |
overlayArrangementEdgeOverlayArrangementEdgeWhere the overlay pane sits: top or bottom. | OverlayArrangementEdge | Where the overlay pane sits: top or bottom. |
onHingeChange(hinge: HingeState | null) => voidThe hinge changed while this view is mounted. | (hinge: HingeState | null) => void | The hinge changed while this view is mounted. |
styleStyleProp<ViewStyle>Defaults to flex: 1. | StyleProp<ViewStyle> | Defaults to flex: 1. |
swiftStyleOneNativeStyleSwiftUI modifiers for the arrangement. | OneNativeStyle | SwiftUI modifiers for the arrangement. |
testIDstringTest identifier. | string | Test identifier. |
Edit this page on GitHub.