iPhoneのノッチやDynamic Island、Androidのステータスバーやナビゲーションバー。デバイスごとに異なる「表示が遮られる領域」をアプリ側で正確に把握しなければ、UIの一部がユーザーに見えなくなります。react-native-safe-area-contextが提供するuseSafeAreaInsetsフックは、この問題をJavaScriptレイヤーから柔軟に解決する手段です。
セーフエリア(Safe Area)の概要
セーフエリアとは、端末のハードウェア要素やシステムUIに遮られず、アプリのコンテンツを安全に表示できる画面領域です。iPhoneではノッチ・Dynamic Island・ホームインジケーターが該当し、Androidではステータスバー・ナビゲーションバー・ディスプレイカットアウト(パンチホールカメラなど)がセーフエリア外に位置します。
これらの領域サイズはデバイスごとに異なるため、ハードコーディングでは対応できません。OSが提供するセーフエリア情報をランタイムに取得し、レイアウトに反映する仕組みが必要です。
useSafeAreaInsetsフックの基礎
インストールと初期設定
useSafeAreaInsetsはreact-native-safe-area-contextライブラリに含まれるReact Hooksです。2026年2月時点の最新バージョンはv5.6.2で、iOS・Android・Web・macOS・Windowsをサポートしています。
Expoプロジェクトの場合:
npx expo install react-native-safe-area-context
素のReact Nativeプロジェクトの場合:
npm install react-native-safe-area-context
# iOS
cd ios && pod install
フックを利用するには、アプリのルートコンポーネントをSafeAreaProviderで囲む必要があります。
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function App() {
return (
<SafeAreaProvider>
{/* アプリ全体のコンポーネントツリー */}
</SafeAreaProvider>
);
}
SafeAreaProviderはReact Contextを通じてセーフエリアの値を配下のコンポーネントへ伝搬します。react-native-screensを使用している場合、モーダルやルートコンポーネントごとにもSafeAreaProviderを配置してください。
戻り値(EdgeInsets)の構造
useSafeAreaInsets()はEdgeInsetsオブジェクトを返します。
const insets = useSafeAreaInsets();
// insets.top: number - 上端のインセット(ステータスバー・ノッチ領域)
// insets.bottom: number - 下端のインセット(ホームインジケーター領域)
// insets.left: number - 左端のインセット(横画面時のノッチ領域など)
// insets.right: number - 右端のインセット(横画面時のノッチ領域など)
各プロパティの値はピクセル単位(dp)で、デバイスの物理的な遮蔽領域の大きさを表します。縦画面のiPhoneではtopとbottomに値が入り、leftとrightは0になるのが一般的です。横画面に回転するとleft・rightにもノッチ分の値が反映されます。
値の更新は非同期で行われるため、画面回転時にはわずかな遅延が生じる点に注意してください。
基本的な使い方
import { View, Text, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function HomeScreen() {
const insets = useSafeAreaInsets();
return (
<View style={[styles.container, {
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
}]}>
<Text>コンテンツがセーフエリア内に表示されます</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ffffff',
},
});
Math.maxを使えば、セーフエリアと最小パディングのいずれか大きい方を適用できます。
<View style={{ paddingBottom: Math.max(insets.bottom, 16) }}>
<Text>最低16dpのパディングを保証</Text>
</View>
SafeAreaViewとuseSafeAreaInsetsの比較
react-native-safe-area-contextはセーフエリアを扱うための手段を複数提供しています。目的によって適切な手段が異なります。
| 観点 | SafeAreaView | useSafeAreaInsets | SafeAreaInsetsContext.Consumer |
|---|---|---|---|
| 利用形態 | コンポーネント | React Hooks | Render Props |
| 適用方法 | 自動でpaddingを付与 | 手動でstyleに反映 | 手動でstyleに反映 |
| 辺の指定 | edgesプロップで選択可 | JavaScript側で自由に制御 | JavaScript側で自由に制御 |
| レイアウト自由度 | 低(padding固定) | 高(margin・position等も可) | 高 |
| 回転時の挙動 | ネイティブ側で即時反映 | 非同期更新のためわずかに遅延 | 非同期更新のためわずかに遅延 |
| クラスコンポーネント | 利用可 | 利用不可 | 利用可 |
| 主な用途 | 画面全体のラッピング | ヘッダー・フッター等の部分適用 | クラスコンポーネントでの利用 |
SafeAreaViewを選ぶ場面: 画面全体をセーフエリアで囲むだけで十分なケース。edgesプロップで['top', 'bottom']のように適用する辺を限定できます。回転時のパフォーマンスが優先される場合にも有利です。
useSafeAreaInsetsを選ぶ場面: インセット値をJavaScriptで加工して使いたいケース。ヘッダーの高さにtop insetsを加算する、ボトムタブの高さにbottom insetsを足すなど、きめ細かなレイアウト制御が可能です。React Navigationの公式ドキュメントでも、画面遷移のアニメーション時にちらつきを防ぐためフックの利用が推奨されています。
実装パターン集
カスタムヘッダーに適用する
ナビゲーションのデフォルトヘッダーを使わず、独自のヘッダーを実装する場合はinsets.topを高さに加算します。
import { View, Text, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function CustomHeader({ title }: { title: string }) {
const insets = useSafeAreaInsets();
const HEADER_HEIGHT = 56;
return (
<View style={[styles.header, {
paddingTop: insets.top,
height: HEADER_HEIGHT + insets.top,
}]}>
<Text style={styles.headerTitle}>{title}</Text>
</View>
);
}
const styles = StyleSheet.create({
header: {
backgroundColor: '#6200ee',
justifyContent: 'center',
alignItems: 'center',
},
headerTitle: {
color: '#ffffff',
fontSize: 18,
fontWeight: 'bold',
},
});
ボトムタブ・フローティングボタンへの適用
画面下部に固定配置する要素にはinsets.bottomを反映します。
function BottomActions() {
const insets = useSafeAreaInsets();
return (
<View style={[styles.bottomBar, {
paddingBottom: Math.max(insets.bottom, 12),
}]}>
<TouchableOpacity style={styles.actionButton}>
<Text style={styles.buttonText}>送信</Text>
</TouchableOpacity>
</View>
);
}
ホームインジケーターが存在しないAndroid端末ではinsets.bottomが0になるため、Math.maxで最小値を設定しておくとプラットフォーム間でUIの一貫性を保てます。
スクロール画面での活用
FlatListやScrollViewで全画面コンテンツを表示する場合、contentContainerStyleにインセットを反映します。
import { FlatList } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function ItemList({ data }: { data: Item[] }) {
const insets = useSafeAreaInsets();
return (
<FlatList
data={data}
renderItem={({ item }) => <ItemRow item={item} />}
keyExtractor={(item) => item.id}
contentContainerStyle={{
paddingTop: insets.top,
paddingBottom: insets.bottom,
}}
/>
);
}
FlatListをSafeAreaViewの中に入れてさらにScrollViewでラップすると、「VirtualizedLists should never be nested inside plain ScrollViews」という警告が出ます。useSafeAreaInsetsでcontentContainerStyleに直接paddingを指定すれば、この問題を回避できます。
横画面(ランドスケープ)での対応
横画面ではノッチが左右のいずれかに位置するため、insets.leftとinsets.rightの両方を考慮します。
function LandscapeContent() {
const insets = useSafeAreaInsets();
return (
<View style={{
flex: 1,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: Math.max(insets.left, 16),
paddingRight: Math.max(insets.right, 16),
}}>
<Text>横画面でもコンテンツが遮られません</Text>
</View>
);
}
端末を回転させるとインセット値が非同期に更新されます。レイアウトの切り替え時にちらつきが気になる場合は、回転アニメーション中にコンテンツをフェードさせるといった工夫が有効です。
ExpoおよびExpo Routerでの導入
Expo SDKを利用するプロジェクトではreact-native-safe-area-contextがpeer dependencyとして含まれていることが多く、Expo Routerを使っている場合は自動的にSafeAreaProviderがセットアップされます。
Expo Router環境での確認ポイント:
- Expo Routerはルートレイアウトに
SafeAreaProviderを自動で組み込みます。手動でラップする必要はありません - 個別画面でセーフエリアを制御したい場合は、各画面コンポーネント内で
useSafeAreaInsetsを呼び出します
// app/_layout.tsx(Expo Router利用時)
import { Stack } from 'expo-router';
export default function RootLayout() {
// SafeAreaProviderはExpo Routerが自動提供
return <Stack />;
}
// app/index.tsx
import { View, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
export default function HomeScreen() {
const insets = useSafeAreaInsets();
return (
<View style={{ flex: 1, paddingTop: insets.top }}>
<Text>Expo Routerプロジェクトの画面</Text>
</View>
);
}
Expo Routerを使わないExpoプロジェクトでは、App.tsxでSafeAreaProviderを手動で配置してください。
React Navigationとの連携
React Navigationはデフォルトでヘッダーやタブバーにセーフエリアを自動適用します。しかし、以下のケースではuseSafeAreaInsetsによる手動対応が必要です。
headerShown: falseでヘッダーを非表示にしている画面tabBarを独自コンポーネントに置き換えている場合presentation: 'modal'で表示する画面のコンテンツ部分
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
const Stack = createNativeStackNavigator();
function FullScreenPage() {
const insets = useSafeAreaInsets();
return (
<View style={{
flex: 1,
paddingTop: insets.top,
paddingBottom: insets.bottom,
}}>
<Text>ヘッダーなしの全画面ページ</Text>
</View>
);
}
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="FullScreen"
component={FullScreenPage}
options={{ headerShown: false }}
/>
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
}
React Navigationの公式ドキュメントでは、SafeAreaViewコンポーネントよりもuseSafeAreaInsetsフックの利用が推奨されています。画面遷移アニメーション中のレイアウトのちらつきを防げるためです。
iOS・Androidのプラットフォーム差異
セーフエリアのインセット値はプラットフォームとデバイスによって大きく異なります。
| デバイス例 | top | bottom | left | right |
|---|---|---|---|---|
| iPhone 16 Pro(縦画面) | 62 | 34 | 0 | 0 |
| iPhone SE 3(縦画面) | 20 | 0 | 0 | 0 |
| Android(ステータスバーあり・ナビゲーションバーあり) | 24〜48 | 48 | 0 | 0 |
| Android(ジェスチャーナビゲーション) | 24〜48 | 0〜20 | 0 | 0 |
iOSの特徴:
- ノッチ/Dynamic Island搭載機では
topが44〜62程度 - ホームインジケーター搭載機では
bottomが34程度 - ホームボタン搭載機(iPhone SE等)では
topが20、bottomが0
Androidの特徴:
- ステータスバーの高さはメーカー・OSバージョンにより異なる
- Android 15以降は
edge-to-edge表示がデフォルトとなり、セーフエリア対応の重要性が増している - 3ボタンナビゲーションとジェスチャーナビゲーションで
bottomの値が変わる
useSafeAreaInsetsはこれらの差異をランタイムに吸収するため、プラットフォームごとの条件分岐を書く必要がありません。
よくあるトラブルと解決策
insetsがすべて0を返す
useSafeAreaInsetsが{ top: 0, bottom: 0, left: 0, right: 0 }を返す場合、ほとんどがSafeAreaProviderの配置漏れが原因です。
チェックリスト:
- アプリのルートに
SafeAreaProviderが配置されているか react-native-screensを使用している場合、モーダルのルートにもSafeAreaProviderを追加しているか- Expo Routerを使っているなら追加不要(自動で提供されます)
- テスト環境(Jest等)で実行している場合は
initialWindowMetricsのモック設定が必要
テスト時のモック例:
import { SafeAreaProvider } from 'react-native-safe-area-context';
const mockInsets = {
frame: { x: 0, y: 0, width: 393, height: 852 },
insets: { top: 59, bottom: 34, left: 0, right: 0 },
};
function renderWithSafeArea(component: React.ReactElement) {
return render(
<SafeAreaProvider initialMetrics={mockInsets}>
{component}
</SafeAreaProvider>
);
}
Android端末で正しく動作しない
Android固有の問題として以下が報告されています。
translucent設定の影響:StatusBarコンポーネントでtranslucent={true}を設定していない場合、ステータスバー分の高さがinsetsに含まれないことがあります- Android 15のedge-to-edge対応:
react-native-safe-area-contextv5系で対応済みですが、古いバージョンでは正しい値が取得できない場合があります。v5.6.2以降へのアップデートを推奨します - エミュレーターでの差異: 物理デバイスと異なるインセット値が返される場合があります。実機での動作確認が確実です
クラスコンポーネントで利用したい場合
useSafeAreaInsetsはReact Hooksのため、クラスコンポーネントから直接呼び出せません。代わりにSafeAreaInsetsContext.Consumerを使用します。
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
class LegacyScreen extends React.Component {
render() {
return (
<SafeAreaInsetsContext.Consumer>
{(insets) => (
<View style={{
flex: 1,
paddingTop: insets?.top ?? 0,
paddingBottom: insets?.bottom ?? 0,
}}>
<Text>クラスコンポーネントでのセーフエリア対応</Text>
</View>
)}
</SafeAreaInsetsContext.Consumer>
);
}
}
あるいは、インセット値を注入するHOC(Higher-Order Component)を作成する方法もあります。
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import type { EdgeInsets } from 'react-native-safe-area-context';
function withSafeAreaInsets<P extends { insets: EdgeInsets }>(
WrappedComponent: React.ComponentType<P>
) {
return function WithInsets(props: Omit<P, 'insets'>) {
const insets = useSafeAreaInsets();
return <WrappedComponent {...(props as P)} insets={insets} />;
};
}
パフォーマンスを高める工夫
initialWindowMetricsで初回描画を高速化
SafeAreaProviderにinitialMetricsを渡すと、ネイティブモジュールの初期化を待たずにインセット値を即座に利用できます。初回描画時のレイアウトシフトが軽減されます。
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context';
export default function App() {
return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
{/* アプリ本体 */}
</SafeAreaProvider>
);
}
initialWindowMetricsはネイティブ側でアプリ起動時に計測された値です。Web環境やSSR環境ではnullになるため、サーバーサイドレンダリング時にはフォールバック値の設定が必要です。
不要な再レンダリングを避ける
useSafeAreaInsetsはインセット値が変化するたびに再レンダリングを発生させます。通常の縦画面操作では値が変わることはありませんが、画面回転をサポートするアプリでは注意が必要です。
インセット値を使わない子コンポーネントが不要に再レンダリングされないよう、React.memoやコンポーネント分割を活用してください。
// インセット値を受け取るコンポーネントと、内部のコンテンツを分離
function ScreenWithInsets() {
const insets = useSafeAreaInsets();
return (
<View style={{ flex: 1, paddingTop: insets.top }}>
<HeavyContent />
</View>
);
}
const HeavyContent = React.memo(function HeavyContent() {
// このコンポーネントはinsetsの変化では再レンダリングされない
return <ExpensiveList />;
});
まとめ
useSafeAreaInsetsは、React Nativeアプリで各デバイスのセーフエリアをJavaScriptから柔軟に制御できるフックです。SafeAreaViewが画面全体のラッピングに適している一方、フックを使えばヘッダー・フッター・スクロールコンテンツなど部分的な領域に対してインセット値を自由に適用できます。
導入時のポイントを整理します。
SafeAreaProviderの配置を忘れない — ルートコンポーネントに1つ、モーダル使用時は各モーダルにも配置- Expo Routerなら追加設定不要 —
SafeAreaProviderは自動提供される Math.maxで最小パディングを確保 — プラットフォーム間のUI一貫性を保つinitialWindowMetricsで初回描画を最適化 — レイアウトシフトを軽減- React Navigationでは
SafeAreaViewよりフックを優先 — 画面遷移時のちらつきを防止 - クラスコンポーネントでは
SafeAreaInsetsContext.Consumerを使用
公式ドキュメント:
