Safe area

SafeArea reports how much of each edge belongs to the system: the notch, the status bar, the home indicator, a rounded corner, and on Android the display cutout and the navigation bar.

Import
import { SafeArea } from '@ng-native/device';
Inject
inject(SafeArea)
import { Component, inject } from '@angular/core';
import { SafeArea } from '@ng-native/device';

@Component({
  selector: 'app-floating-button',
  template: `<pressable
    [style.marginBottom]="safeArea.insets().bottom + 16"
    (press)="onPress()"
  />`,
})
export class FloatingButton {
  protected readonly safeArea = inject(SafeArea);

  protected onPress(): void {}
}

Unlike every other service in this package, SafeArea has no React Native module behind it and no SOURCE token to override in a test. Insets are a property of a view, not of the device, and the only thing that can report them is a native view inside the window: <safe-area-provider> in @ng-native/components, which measures itself and calls report(). That is also why the values start at zero - nothing is known until a view has been laid out, which is one frame after the app mounts.

insets is { top, right, bottom, left }, in points, and starts at all zeroes until the provider has measured. frame is the provider's own frame - the area it was handed to lay out in, which on Android 15 includes the system bars because the app draws behind them. Screen.window reads frame rather than Dimensions for that reason; see the screen page. known() tells you whether a real measurement has arrived yet, for a layout that would rather wait than jump.

Most layouts should not read SafeArea at all. A <safe-area-view> applies the insets natively, in the same layout pass as everything else, without a round trip through JavaScript, and a screen in a native stack has its header do it. Inject SafeArea for what neither covers: a floating button that must clear the home indicator, a scroll view computing its own content inset, a sheet drawing its own chrome.

report() is called by <safe-area-provider>, not by an app. An inset written from anywhere else is a number that stops matching the screen the moment the device rotates.

Off a device and on the web

Off a device insets stays at all zeroes and known() stays false forever, because nothing ever calls report() without a provider laid out somewhere. On the web there is no equivalent concept, so an app that only targets native should still guard behaviour that depends on known() rather than assuming a provider is always mounted above it.

Reference

SafeAreaservice@ng-native/device
import { SafeArea } from '@ng-native/device';

Signals

known
unknown

Whether anything has reported yet, for a layout that would rather wait than jump.

Methods

report(insets: Insets, frame: Frame): void

Called by <safe-area-provider> when native reports. Not for apps: an inset written from anywhere else is a number that stops matching the screen the moment the device rotates.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.