Expo

Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법

2026.04.22·수정 2026.05.12·약 14분

이 글에서 정리하는 내용

Expo에서 safe area를 왜 써야 하는지부터 시작해, SafeAreaProvider를 어디에 두는지, SafeAreaView와 useSafeAreaInsets를 언제 나눠 써야 하는지, 그리고 스크롤 화면·고정 버튼·커스텀 헤더에서 어떻게 적용하면 덜 꼬이는지까지 한 번에 정리합니다.

Safe Area를 먼저 이해하기

Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법 핵심 개념을 설명하는 첫 번째 본문 이미지

처음 Expo에서 safe area를 볼 때 가장 헷갈렸던 지점은 이게 단순히 아이폰 노치 대응 정도로만 보였다는 점이었습니다. 그런데 실제로 화면을 만들다 보면 상단 상태바, 하단 홈 인디케이터, 안드로이드 시스템 영역 때문에 콘텐츠가 생각보다 쉽게 잘립니다. 그래서 safe area는 예쁘게 띄워 보이기 위한 옵션이 아니라, 화면이 시스템 UI와 부딪히지 않게 만드는 기본 레이아웃 기준으로 보는 편이 맞습니다.

특히 웹에서 넘어오면 padding-top이나 padding-bottom을 직접 주는 방식으로 먼저 해결하고 싶어집니다. 하지만 기기마다 안전 영역 크기가 다르고, 회전이나 기종 차이까지 고려하면 하드코딩만으로는 금방 한계가 드러납니다. 이 글에서는 그래서 Expo에서 safe area를 어떤 구조로 잡아야 덜 흔들리는지, 어떤 화면에서는 자동 처리만으로 충분하고 어떤 화면에서는 직접 inset 값을 꺼내 써야 하는지를 순서대로 정리합니다.

왜 단순 padding으로 끝나지 않는가

// 잘못된 출발 예시입니다.
// 기기마다 상단 안전 영역이 다를 수 있는데 고정값으로 밀어두고 있습니다.
<View style={{ paddingTop: 44, paddingBottom: 24 }}> <Text>화면 내용</Text>
</View>

이 방식은 특정 기기에서는 얼핏 맞아 보일 수 있지만, 다른 기기나 가로 모드, 안드로이드 환경에서는 금방 어색해집니다. 그래서 safe area는 고정 숫자를 먼저 넣는 문제가 아니라, 현재 기기의 안전 영역 값을 기준으로 레이아웃을 계산하는 문제라고 이해하는 편이 실무에서 더 안전했습니다.

Expo에서 잡아야 하는 기본 구조

Expo에서는 보통 앱 루트에서 SafeAreaProvider를 먼저 두고, 각 화면에서 그 값을 소비하는 방식으로 구조를 잡습니다. 이 순서를 먼저 잡아두면 이후 화면 단위에서 SafeAreaView를 쓰든, useSafeAreaInsets를 쓰든 기준이 분명해집니다. Expo Router를 쓰는 경우라면 루트 레이아웃에서 감싸는 구성이 가장 먼저 떠올리기 좋은 시작점입니다.

루트 레이아웃에 SafeAreaProvider 두기

// app/_layout.tsx 예시입니다.
// 앱 전체에서 safe area 값을 읽을 수 있도록 루트에서 Provider를 감쌉니다.
import { Stack } from 'expo-router';
import { SafeAreaProvider } from 'react-native-safe-area-context'; export default function RootLayout() { return ( <SafeAreaProvider> <Stack screenOptions={{ headerShown: false }} /> </SafeAreaProvider> );
}

이 구조를 먼저 두는 이유는 safe area 정보를 Context로 공급하는 출발점이 필요하기 때문입니다. 화면마다 따로 해결하려고 하면 어떤 곳은 적용되고 어떤 곳은 빠지는 식으로 기준이 흐려지기 쉽습니다. 반대로 루트에서 시작하면이후 개별 화면은 같은 기준 위에서 필요한 만큼만 여백을 가져갈 수 있습니다.

여기서 한 단계 더 보면, Provider는 앱 루트에 한 번 두는 것이 기본이지만 모달 루트나 react-native-screens를 사용하는 특정 라우트 루트에서는 추가 Provider가 필요한 경우도 있습니다. 즉 safe area는 화면 안에서만 처리하는 문제가 아니라, 어느 트리에서 inset 값을 공급받는지까지 함께 보는 편이 안정적입니다.

SafeAreaView와 useSafeAreaInsets 차이

// 단순한 화면이라면 SafeAreaView로 시작하기 좋습니다.
// safe area 여백을 자동 padding처럼 적용합니다.
import { SafeAreaView } from 'react-native-safe-area-context';
import { Text } from 'react-native'; export default function HomeScreen() { return ( <SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}> <Text>홈 화면</Text> </SafeAreaView> );
}

화면 전체를 안전 영역 안에 넣고 싶다면 SafeAreaView가 가장 단순합니다. 자동으로 inset을 padding처럼 더해주기 때문에, 목록 화면이나 일반 콘텐츠 화면처럼 구조가 단순할수록 빠르게 적용하기 좋습니다. 대신 이미 상위에서 padding을 주고 있거나, 특정 방향만 세밀하게 제어해야 하는 경우에는 여백이 중복되기 쉽습니다.

세밀한 제어가 필요할 때는 inset 값을 직접 꺼내기

// 하단 고정 버튼처럼 세밀한 제어가 필요한 경우입니다.
// safe area 값을 직접 읽어서 필요한 위치에만 더합니다.
import { View, Pressable, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function CheckoutScreen() { const insets = useSafeAreaInsets(); return ( <View style={{ flex: 1 }}> <View style={{ flex: 1 }}> <Text>주문 내용</Text> </View> <Pressable style={{ margin: 16, paddingVertical: 14, paddingBottom: 14 + insets.bottom, borderRadius: 12}} > <Text>결제하기</Text> </Pressable> </View> );
}

하단 CTA 버튼, 커스텀 탭, 떠 있는 패널처럼 일부 요소에만 안전 영역을 반영해야 할 때는 useSafeAreaInsets가 더 잘 맞습니다. top, right, bottom, left 값을 직접 가져와 필요한 위치에만 반영할 수 있기 때문입니다. 다만 이 방식은 유연한 대신, 어디에 얼마를 더했는지 개발자가 직접 관리해야 하므로 구조가 복잡해질수록 중복 적용을 더 조심해서 봐야 합니다.

여기서 구분해서 볼 점도 있습니다. react-native-safe-area-context 문서에서는 일반적으로 SafeAreaView를 더 선호되는 소비 방식으로 소개합니다. 반면 React Navigation 문서에서는 커스텀 헤더, 커스텀 탭 바, 애니메이션 전환이 있는 화면에서는 useSafeAreaInsets 쪽이 더 일관되다고 안내합니다. 그래서 단순 화면은 SafeAreaView, 네비게이션 UI와 강하게 맞물리는 화면은 useSafeAreaInsets라는 식으로 판단하면 실무 감각에 더 가깝습니다.

방식 언제 어울리는가
SafeAreaView 전체 화면을 안전 영역 안에 단순하게 넣고 싶을 때
useSafeAreaInsets 하단 버튼, 커스텀 헤더처럼 일부 영역만 정밀하게 제어할 때

실무에서 자주 쓰는 패턴

Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법 적용 흐름을 설명하는 두 번째 본문 이미지
// 스크롤 본문 + 하단 고정 버튼 조합 예시입니다.
// 본문은 스크롤되고, 버튼만 하단 안전 영역을 반영합니다.
import { View, ScrollView, Pressable, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function FormScreen() { const insets = useSafeAreaInsets(); return ( <View style={{ flex: 1 }}> <ScrollView contentContainerStyle={{ padding: 16, paddingBottom: 120 }}> <Text>입력 폼 내용</Text> </ScrollView> <View style={{ paddingHorizontal: 16, paddingTop: 12, paddingBottom: 12 + insets.bottom}} > <Pressable style={{ paddingVertical: 14, borderRadius: 12 }}> <Text>저장하기</Text> </Pressable> </View> </View> );
}

실무에서 많이 만나는 패턴은 화면 전체를 SafeAreaView로 감싸는 경우보다, 스크롤 본문과 하단 고정 버튼을 분리해서 다루는 경우였습니다. 본문은 충분한 contentContainerStyle 여백을 주고, 실제 하단 안전 영역은 버튼 박스 쪽에서만 더해주면 구조가 비교적 깔끔하게 유지됩니다.

커스텀 헤더를 직접 만들 때도 같은 원리로 보면 편합니다. 헤더 전체를 무조건 큰 paddingTop으로 미는 대신, top inset을 이용해 헤더 상단 여백만 계산하면 기기별 차이를 더 자연스럽게 흡수할 수 있습니다. 반대로 네비게이션 라이브러리가 이미 처리하는 헤더 위에 다시 여백을 더하면 화면이 과하게 내려가므로, 누가 safe area를 담당하는지 먼저 정리하는 습관이 중요합니다. React Navigation의 기본 헤더와 기본 탭 바는 safe area를 어느 정도 처리하지만, 직접 숨기고 커스텀 UI를 올리는 순간부터는 개발자가 책임지고 여백을 계산해야 합니다.

상단만 안전 영역을 적용하고 싶을 때

// 상단 헤더만 safe area를 받고 본문은 꽉 채우고 싶은 경우입니다.
// edges로 필요한 방향만 골라 적용할 수 있습니다.
import { SafeAreaView } from 'react-native-safe-area-context';
import { View, Text } from 'react-native'; export default function ProfileScreen() { return ( <View style={{ flex: 1 }}> <SafeAreaView edges={['top']} style={{ paddingHorizontal: 16 }}> <Text>프로필</Text> </SafeAreaView> <View style={{ flex: 1 }}> <Text>본문 내용</Text> </View> </View> );
}

이 패턴은 헤더만 노치와 상태바를 피하고, 본문 배경은 화면 끝까지 채우고 싶을 때 자주 씁니다. edges를 쓰면 top만 적용하거나 bottom만 적용하는 식으로 범위를 좁힐 수 있어서, 화면 전체를 무조건 한 번에 감싸는 방식보다 의도를 더 또렷하게 표현할 수 있습니다.

중복 padding이 생기기 쉬운 예시

// 상위 SafeAreaView와 하위 paddingTop이 함께 들어간 경우입니다.
// 화면이 필요 이상으로 아래로 밀릴 수 있습니다.
<SafeAreaView style={{ flex: 1 }}> <View style={{ paddingTop: 24 }}> <Text>제목</Text> </View>
</SafeAreaView>

safe area가 안 먹는 문제만큼 자주 만나는 것이 safe area가 과하게 먹는 문제입니다. 상위에서 이미 안전 영역을 반영했는데 하위 컴포넌트에서 상태바 높이를 다시 고려하거나, 디자인 여백과 안전 영역 여백을 섞어버리면 실제 기기에서 제목이 너무 아래로 내려가 보이게 됩니다. 그래서 safe area 여백과 일반 디자인 여백은 의도를 나눠서 보는 편이 유지보수에 유리했습니다.

정리

Expo에서 safe area를 정리할 때 출발점은 단순합니다. 먼저 루트에 SafeAreaProvider를 두고, 전체 화면을 안전하게 감싸고 싶을 때는 SafeAreaView를, 특정 위치를 정밀하게 제어해야 할 때는 useSafeAreaInsets를 고르는 방식으로 생각하면 흐름이 선명해집니다.

정리하면 이렇게 기억하면 됩니다. 화면 전체 자동 처리에는 SafeAreaView, 하단 버튼·커스텀 헤더·모달처럼 일부 영역 제어에는 useSafeAreaInsets, 그리고 가장 먼저 확인할 것은 루트 Provider와 padding 중복 여부입니다. 이 기준만 잡혀도 safe area 때문에 화면이 잘리거나 과하게 밀리는 문제를 훨씬 덜 겪게 됩니다.

많이 받는 질문

Q. Expo에서 React Native 기본 SafeAreaView를 써도 되나요?
가능하더라도 기준으로 삼기에는 적합하지 않습니다. 현재는 react-native-safe-area-context 기준으로 잡는 편이 더 안전하고, Expo 문서도 그 흐름으로 안내합니다.

Q. 모든 화면을 SafeAreaView로 감싸야 하나요?
항상 그렇지는 않습니다. 단순한 화면은 그렇게 시작하기 좋지만, 스크롤 본문과 하단 고정 버튼이 분리된 화면이나 커스텀 헤더 화면은 inset 값을 직접 꺼내 일부만 반영하는 쪽이 더 자연스러울 수 있습니다.

Q. Android에서도 safe area를 꼭 신경 써야 하나요?
그렇습니다. 아이폰 노치만의 문제가 아니라 상태바, 하단 시스템 영역, 기기별 UI 차이까지 함께 보게 되므로 Android도 같은 기준으로 확인하는 편이 좋습니다.

같이 읽으면 좋은 글

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

“Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법”에 대한 4개의 생각

댓글 남기기