# Safe Area Insets

## Introduction

Mobile applications must avoid overlapping UI elements with system components: status bars, notches, home indicators, and navigation bars.

## Best practices

- **Always use safe area insets:** Prevent content from being obscured by system UI
- **Test on multiple devices:** Different devices have varying safe area requirements
- **Handle orientation changes:** Safe areas differ between portrait and landscape
- **Apply insets consistently:** Use theme-level configuration for uniform spacing

## The problem

A basic `CallContent` component usage without safe area handling:

```tsx
import React from "react";
import { CallContent } from "@stream-io/video-react-native-sdk";
import { StyleSheet, View } from "react-native";

export const ActiveCall = () => {
  // other code omitted for brevity

  return (
    <View style={styles.container}>
      <CallContent />
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1 },
});
```

Result: UI elements overlap with system areas:

![UI elements overlapping with system areas](https://getstream.io/docs-assets/images/04a3d764e363.png)

## Recommended setup

Configure safe area handling once near your app root:

1. Add `SafeAreaProvider`
2. Enable Android edge-to-edge display
3. Map safe area insets to the SDK theme
4. Pass the theme to `StreamVideo` or `StreamTheme`

### Add `SafeAreaProvider`

Use `insets` from `react-native-safe-area-context`. This package automatically calculates padding values based on the device's notch, home indicator, and status bar.

`react-native-safe-area-context` is included by default in Expo. For non-Expo projects:

```bash
npm install react-native-safe-area-context
```

Wrap your app root with `SafeAreaProvider`:

```tsx
import { SafeAreaProvider } from "react-native-safe-area-context";

export const Root = () => (
  <SafeAreaProvider>
    <App />
  </SafeAreaProvider>
);
```

### Enable Android edge-to-edge display

On Android, enable edge-to-edge display so the system bars and safe-area values match:

**Expo**

Set `edgeToEdgeEnabled` in your app config. This keeps the project explicit and enables edge-to-edge display on supported Android versions:

```json
{
  "expo": {
    "android": {
      "edgeToEdgeEnabled": true
    }
  }
}
```

**React Native CLI**

#### React Native 0.81 and newer

Enable the Gradle flag in `android/gradle.properties`:

```properties
edgeToEdgeEnabled=true
```

If you need to control the Android navigation bar style, install `@zoontek/react-native-navigation-bar`:

```bash
npm install @zoontek/react-native-navigation-bar
```

#### Older React Native versions

Install [`react-native-edge-to-edge`](https://github.com/zoontek/react-native-edge-to-edge):

```bash
npm install react-native-edge-to-edge
```

Update your Android app theme. See the [`react-native-edge-to-edge` README](https://github.com/zoontek/react-native-edge-to-edge#pick-a-parent-theme) to pick the matching theme for your app:

```xml
<resources>
  <style name="AppTheme" parent="Theme.EdgeToEdge">
    <item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
  </style>
</resources>
```

### Map insets to the SDK theme

Read the current insets and pass them to `theme.variants.insets`:

```tsx
import { DeepPartial, Theme } from "@stream-io/video-react-native-sdk";
import { useSafeAreaInsets } from "react-native-safe-area-context";

export const useCustomTheme = (): DeepPartial<Theme> => {
  const { top, right, bottom, left } = useSafeAreaInsets();

  const variants: DeepPartial<Theme["variants"]> = {
    insets: {
      top,
      right,
      bottom,
      left,
    },
  };

  const customTheme: DeepPartial<Theme> = {
    variants,
  };

  return customTheme;
};
```

### Pass the theme to the SDK

Provide the custom theme to the `style` prop of [`StreamVideo`](https://getstream.io/video/docs/react-native/v2/ui-components/core/stream-video/#style):

```tsx
import {
  StreamVideo,
  StreamVideoClient,
} from "@stream-io/video-react-native-sdk";
import { useCustomTheme } from "../theme";

export const App = () => {
  const client = StreamVideoClient.getOrCreateInstance(/* ... */);
  const customTheme = useCustomTheme();

  return (
    <StreamVideo client={client} style={customTheme}>
      <MyUI />
    </StreamVideo>
  );
};
```

Alternatively, pass the prop to `StreamTheme` to wrap specific components:

```tsx
import {
  StreamTheme,
  StreamVideo,
  StreamVideoClient,
} from "@stream-io/video-react-native-sdk";
import { useCustomTheme } from "../theme";

export const App = () => {
  const client = StreamVideoClient.getOrCreateInstance(/* ... */);
  const customTheme = useCustomTheme();

  return (
    <StreamVideo client={client}>
      <StreamTheme style={customTheme}>
        <MyUI />
      </StreamTheme>
    </StreamVideo>
  );
};
```

Without additional code changes, the insets are properly applied:

![Insets applied](https://getstream.io/docs-assets/images/39fc420305ff.png)

### Avoid double padding

After the SDK receives `theme.variants.insets`, full-screen SDK components apply those values internally. Avoid wrapping `CallContent`, `RingingCallContent`, `HostLivestream`, or `ViewerLivestream` in `SafeAreaView`, because that can add padding twice.

### Custom controls and overlays

If you replace SDK controls with custom UI, keep the SDK's inset padding in mind.

When you render your own top bar outside `CallContent`, the SDK's default top padding can create extra space between your top bar and the video layout. Scope the override to that `CallContent` instance instead of changing your root theme:

```tsx
import React, { PropsWithChildren } from "react";
import { StreamTheme, useTheme } from "@stream-io/video-react-native-sdk";

const CallContentWithoutTopInset = ({ children }: PropsWithChildren) => {
  const { theme } = useTheme();
  const themeOverride = {
    ...theme,
    callContent: {
      ...theme.callContent,
      container: {
        ...theme.callContent.container,
        paddingTop: 0,
      },
    },
  };

  return <StreamTheme theme={themeOverride}>{children}</StreamTheme>;
};
```

```tsx
<CustomTopBar />
<CallContentWithoutTopInset>
  <CallContent CallControls={CustomBottomControls} />
</CallContentWithoutTopInset>
```

When your custom bottom controls render absolute overlays, subtitles, or drawers, include the bottom inset in your positioning math. `CallContent` already adds bottom safe-area padding, so offsets measured from custom controls often need `theme.variants.insets.bottom` too:

```tsx
const {
  theme: {
    variants: { insets },
  },
} = useTheme();

const subtitleBottom = controlsHeight + insets.bottom;
const drawerOffset = -controlsHeight - insets.bottom;
```

---

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react-native/v2/ui-cookbook/safe-area-insets/](https://getstream.io/video/docs/react-native/v2/ui-cookbook/safe-area-insets/).