Expo StatusBar 스타일과 Safe Area 여백은 어떻게 나누어 설정하나요?
StatusBar는 상단 시간·배터리 아이콘의 표시 스타일, Safe Area는 콘텐츠 여백을 담당합니다. 밝은 배경에는 dark, 어두운 배경에는 light를 적용해 읽을 수 있는 대비를 만듭니다.
적용 조건·범위: Android의 backgroundColor·translucent는 설치된 Expo/React Native 버전과 edge-to-edge 적용 조건을 함께 봅니다. React Native 0.79 문서는 Android 15의 edge-to-edge 강제 적용과 관련해 API 35에서 두 설정이 deprecated이며 효과가 없다고 설명합니다. 이 문구를 모든 기존 Android 앱의 동일 동작으로 확대하지 말고, 실제 프로젝트의 SDK·대상 API와 기기 결과를 확인하세요. 본문 예제는 화면 배경과 Safe Area로 배치하고 StatusBar는 스타일·가시성 제어에 사용합니다.
본문 근거: 해당 기능의 Expo 공식 문서
이 글에서 정리하는 내용
StatusBar의 style은 상단 시스템 아이콘의 밝기를 바꾸고, 콘텐츠 여백은 Safe Area가 담당합니다. 밝은 배경에는 dark, 어두운 배경에는 light를 적용해 대비를 확인합니다. 첫 독립 예제는 Router 없는 App.tsx에서 배경과 상태바를 함께 전환하고, 화면별 Router 제어는 뒤에서 구분합니다.
목차
expo-status-bar가 하는 일

이 글에서 먼저 분리해서 보려는 것은 상태바의 역할과 화면 레이아웃의 역할입니다. expo-status-bar는 시간, 배터리, 네트워크 아이콘이 보이는 상단 시스템 영역의 텍스트 색상, 숨김 여부, 애니메이션 같은 표시 상태를 제어할 때 사용하는 도구입니다. 즉 상단에 무엇이 보일지를 제어하는 쪽에 가깝고, 콘텐츠를 얼마나 아래로 밀어야 하는지는 별도 문제입니다.
상태바 스타일과 Safe Area는 서로 다른 역할입니다. 상태바가 밝게 보여야 해서 StatusBar를 만졌는데, 정작 버튼이 노치 아래에 깔리는 문제는 그대로 남아 있는 식입니다. 반대로 상단 padding만 늘렸는데 아이콘 색상은 여전히 배경과 겹쳐 안 보이는 경우도 많습니다. 그래서 expo-status-bar는 표시 스타일, Safe Area는 안전한 배치라는 식으로 먼저 나눠 이해하는 편이 훨씬 덜 헷갈립니다.
React Native StatusBar와의 관계
코드 위치: src/app/detail.tsx. 앞에서 전체 파일로 명시한 경우를 제외하면 해당 구조를 설명하는 부분 예제입니다. 서로 다른 예제의 export default를 한 파일에 합치지 않습니다.
// expo-status-bar는 Expo 환경에 맞춘 기본값을 가진 StatusBar 컴포넌트를 사용합니다.
import { StatusBar } from 'expo-status-bar';
export default function App() {
return <StatusBar style="auto" />;
}
expo-status-bar는 React Native StatusBar와 거의 같은 인터페이스를 사용하지만, Expo 프로젝트에서 바로 쓰기 좋게 정리된 패키지입니다. 그래서 Expo에서는 기본적으로 이 패키지부터 익히는 편이 자연스럽습니다. 이 패키지는 컴포넌트 방식과 imperative API를 모두 제공하지만, 일반적인 화면 구성에서는 컴포넌트 방식이 흐름을 읽기 더 쉽습니다.
가장 많이 쓰는 기본 사용법
가장 먼저 익혀둘 것은 루트 레이아웃에 기본 상태바 스타일을 두는 방식입니다. 앱 전체에 공통으로 적용할 기본값을 하나 두고, 특별한 화면에서만 덮어쓰는 구조가 관리하기 좋습니다. Expo Router를 쓴다면 보통 app/_layout.tsx에서 시작하는 편이 깔끔합니다.
루트 레이아웃에 기본값 두기
코드 위치: src/app/_layout.tsx. 앞에서 전체 파일로 명시한 경우를 제외하면 해당 구조를 설명하는 부분 예제입니다. 서로 다른 예제의 export default를 한 파일에 합치지 않습니다.
// 앱 전체 기본 상태바 스타일을 루트에 둡니다.
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
export default function RootLayout() {
return (
<>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
<StatusBar style="dark" />
</>
);
}
여기서 가장 자주 쓰는 prop은 style, hidden, animated입니다. style은 상태바 텍스트와 아이콘을 밝게 혹은 어둡게 보이게 하는 역할이고, hidden은 상태바 자체를 숨길지 결정합니다. animated는 스타일 변화나 숨김 전환이 바뀔 때 화면이 덜 튀게 해줍니다. 처음에는 prop을 많이 외우기보다 이 세 개만 정확히 익혀도 대부분의 화면은 대응할 수 있습니다.
Android의 backgroundColor·translucent는 설치된 Expo/React Native 버전과 edge-to-edge 적용 조건을 함께 봅니다. React Native 0.79 문서는 Android 15의 edge-to-edge 강제 적용과 관련해 API 35에서 두 설정이 deprecated이며 효과가 없다고 설명합니다. 이 문구를 모든 기존 Android 앱의 동일 동작으로 확대하지 말고, 실제 프로젝트의 SDK·대상 API와 기기 결과를 확인하세요. 본문 예제는 화면 배경과 Safe Area로 배치하고 StatusBar는 스타일·가시성 제어에 사용합니다.
필요할 때만 imperative API 쓰기
코드 위치: src/app/detail.tsx. 앞에서 전체 파일로 명시한 경우를 제외하면 해당 구조를 설명하는 부분 예제입니다. 서로 다른 예제의 export default를 한 파일에 합치지 않습니다.
// expo-status-bar는 명령형 함수도 제공합니다.
// 다만 일반 화면에서는 컴포넌트 방식이 더 읽기 쉽습니다.
import { setStatusBarHidden } from 'expo-status-bar';
function openFullscreen() {
setStatusBarHidden(true, 'fade');
}
function closeFullscreen() {
setStatusBarHidden(false, 'fade');
}
expo-status-bar는 setStatusBarStyle, setStatusBarHidden 같은 명령형 함수도 제공합니다. 영상 전체화면 전환처럼 이벤트 한 번에 맞춰 바로 상태를 바꾸고 싶을 때는 이런 방식이 편할 수 있습니다. 다만 화면 렌더링 흐름 안에서 스타일을 관리하는 일반 화면이라면 컴포넌트 방식이 더 예측 가능하고, 유지보수할 때도 읽기 쉽습니다.
그리고 style의 기본값이 auto라는 점도 알아두면 좋습니다. expo-status-bar에서 auto는 현재 색상 테마에 맞춰 light 또는 dark를 자동으로 고르는 값입니다. 즉 다크 모드와 라이트 모드를 모두 고려한 기본값이 필요할 때 이 auto 값은 꽤 실용적입니다.
| 자주 쓰는 prop | 핵심 역할 |
|---|---|
| style, hidden, animated | 텍스트 색상, 표시 여부, 전환 애니메이션 |
| backgroundColor, translucent | Android 관련 속성이지만 최신 환경에서는 제약을 함께 봐야 함 |
화면별 상태바 제어 방법
src/app/detail.tsx 전체 예제입니다. 헤더가 없는 Router 화면을 기준으로 합니다. 탭·스택에서는 보이지 않는 화면도 마운트되어 있을 수 있으므로 화면 전용 StatusBar는 포커스된 동안만 렌더링합니다. 루트 기본값은 유지하고 다른 화면도 각자의 활성 상태를 기준으로 처리하세요.
코드 위치: src/app/detail.tsx. 앞에서 전체 파일로 명시한 경우를 제외하면 해당 구조를 설명하는 부분 예제입니다. 서로 다른 예제의 export default를 한 파일에 합치지 않습니다.
import { useIsFocused } from '@react-navigation/native';
import { Text } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function DetailScreen() {
const focused = useIsFocused();
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#111827' }}>
{focused && <StatusBar style="light" animated />}
<Text style={{ color: '#fff' }}>상세 화면</Text>
</SafeAreaView>
);
}
상세 화면처럼 배경이 어두운 화면에서는 루트의 스타일을 그대로 쓰면 아이콘이 묻혀 보일 수 있습니다. 이럴 때는 그 화면 안에 StatusBar를 한 번 더 두고 style을 light로 덮어쓰면 됩니다. 저는 이 패턴을 목록 화면과 상세 화면의 분위기가 확실히 다를 때 자주 사용합니다.
여기서 기억할 점은 StatusBar를 여러 개 마운트할 수 있다는 점입니다. 전역 하나, 특정 화면 하나가 동시에 존재해도 이상한 구조는 아닙니다. 다만 컴포넌트 기반 설정과 명령형 함수 기반 설정을 한 흐름에 섞어서 쓰면 다음 렌더 때 값이 덮이면서 흐름을 추적하기 어려워질 수 있으니, 한 화면 안에서는 되도록 컴포넌트 방식으로 통일하는 편이 안정적입니다.
전역 기본값과 화면 전용 값을 같이 쓰는 기준
코드 위치: src/app/detail.tsx. 앞에서 전체 파일로 명시한 경우를 제외하면 해당 구조를 설명하는 부분 예제입니다. 서로 다른 예제의 export default를 한 파일에 합치지 않습니다.
// 밝은 배경 화면은 루트 기본값을 그대로 쓰고,
// 어두운 배경 화면만 개별 StatusBar를 추가합니다.
function ListScreen() {
return <View style={{ flex: 1, backgroundColor: '#ffffff' }} />;
}
function PhotoScreen() {
return (
<View style={{ flex: 1, backgroundColor: '#000000' }}>
<StatusBar style="light" />
</View>
);
}
기준은 단순합니다. 앱 전체에서 대부분 같은 톤이라면 루트에 하나만 두고 끝내면 됩니다. 반대로 특정 화면만 배경색이나 몰입감이 크게 다르다면 그 화면 안에서만 따로 선언하면 됩니다. 모든 화면마다 StatusBar를 습관처럼 넣기보다, 기본값에서 벗어나는 화면에만 추가하는 편이 구조를 읽기 쉽습니다.
Safe Area와 플랫폼 차이
설치는 npx expo install expo-status-bar react-native-safe-area-context입니다. app/profile.tsx의 아래 예제는 SafeAreaView가 bottom을 이미 적용하므로 안쪽 View에 insets.bottom을 다시 더하지 않습니다. Android edge-to-edge에서는 상태바 배경색·translucent 속성으로 공간을 확보하는 방식에 의존하지 말고 화면 배경과 inset을 조합합니다. 웹은 이 네이티브 상태바 색 검증 대상이 아닙니다.

상태바가 예쁘게 보이는 것과 콘텐츠가 안전하게 배치되는 것은 다른 문제입니다. Safe Area는 노치, 상태바, 홈 인디케이터 같은 시스템 영역을 피해 콘텐츠를 배치하기 위한 개념입니다. 그래서 상단 아이콘 색을 바꾸려면 StatusBar를 보고, 헤더나 버튼이 가려지지 않게 하려면 SafeAreaView나 useSafeAreaInsets를 봐야 합니다.
상태바 스타일은 StatusBar로, 여백은 Safe Area로 분리하기
코드 위치: src/app/profile.tsx. 앞에서 전체 파일로 명시한 경우를 제외하면 해당 구조를 설명하는 부분 예제입니다. 서로 다른 예제의 export default를 한 파일에 합치지 않습니다.
import { Text, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function ProfileScreen() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#f9fafb' }}>
<StatusBar style="dark" />
<View style={{ paddingTop: 12, paddingHorizontal: 20, paddingBottom: 16 }}>
<Text>프로필 화면</Text>
</View>
</SafeAreaView>
);
}
이런 식으로 분리하면 책임이 선명해집니다. StatusBar는 상단 시스템 텍스트가 밝을지 어두울지 정하고, SafeAreaView는 화면 바깥쪽 위험 구간을 피하게 해줍니다. 하단 고정 버튼처럼 섬세한 보정이 필요할 때는 useSafeAreaInsets로 값을 직접 더하는 편이 더 유연합니다.
특히 Android에서는 edge-to-edge 기준이 강해지면서, 예전처럼 반투명 상태바만 켜 두고 여백 문제를 대충 넘기기 어려워졌습니다. 게다가 React Native 기본 SafeAreaView는 현재 deprecated로 안내되고 있어서, Expo에서는 react-native-safe-area-context 기준으로 정리하는 편이 더 안전합니다. 그래서 요즘은 Android와 iOS를 따로 외우기보다, 먼저 safe area로 겹침을 막고 그 위에서 status bar style만 조정하는 순서로 생각하면 실수가 줄어듭니다.
결론
expo-status-bar는 상단 시스템 바의 표시 상태를 제어하는 도구이고, 콘텐츠 배치 문제를 직접 해결하는 도구는 아닙니다. 앱 전체 기본값은 루트에 두고, 분위기가 다른 화면만 개별로 덮어쓰며, 겹침 문제는 Safe Area로 분리해서 처리하면 구조가 훨씬 단정해집니다. 이 흐름만 익혀도 상태바 때문에 화면이 어색해 보이거나 상단 여백이 꼬이는 문제를 훨씬 안정적으로 다룰 수 있습니다.
많이 받는 질문
Q. expo-status-bar와 react-native의 StatusBar 중 무엇부터 쓰는 편이 좋나요?
Expo 프로젝트라면 보통 expo-status-bar부터 보는 편이 자연스럽습니다. 인터페이스가 익숙하고 Expo 환경에 맞춘 기본값을 바로 활용할 수 있기 때문입니다.
Q. 상태바를 바꿨는데도 상단 콘텐츠가 노치와 겹칩니다. 왜 그런가요?
상태바 스타일 제어와 안전 영역 보정은 다른 문제이기 때문입니다. 이 경우는 StatusBar보다 SafeAreaView나 useSafeAreaInsets가 필요한 상황입니다.
Q. 모든 화면에 StatusBar를 각각 넣어야 하나요?
그럴 필요는 없습니다. 대부분의 화면이 같은 톤이라면 루트에 기본값 하나만 두고, 특정 화면만 예외적으로 다른 스타일이 필요할 때만 화면 안에 추가하는 편이 관리하기 쉽습니다.
같이 읽으면 좋은 글
- Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법
- Expo Router 사용법: app 폴더와 Stack Tabs 구조 잡기
- Expo WebBrowser 사용법: 외부 링크와 로그인 복귀 처리
문서·검증 기준 (2026-09-13): 해당 기능의 Expo 공식 문서와 코드 구조를 대조했습니다. 설치 버전은 프로젝트 Expo SDK에 맞는 npx expo install 결과와 lockfile을 기준으로 합니다. 이번 개정에서는 실제 Android·iOS 기기 실행, 원격 EAS 빌드, 외부 인증 서버 연동을 수행하지 않았습니다. 본문의 기기 동작은 독자가 확인할 기대 결과입니다.
독립 실습: 배경과 상태바 스타일 함께 전환
React 함수 컴포넌트·props·state와 TypeScript import를 알고 시작합니다. 첫 Expo 앱 환경 준비의 blank-typescript 프로젝트를 별도 연습 폴더에 사용합니다. 생성된 index.ts·package.json·lockfile은 유지하고 아래 App.tsx만 연결합니다. TaskFlow 코드는 필요 없습니다. npx expo start 후 대상 기기에서 확인합니다.
npx expo install expo-status-bar react-native-safe-area-context를 실행합니다. Router가 없는 App.tsx 화면으로 시작합니다.
App.tsx 전체 파일
import { useState } from 'react';
import { Button, Text } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
export default function App() {
const [dark, setDark] = useState(false);
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1, padding: 24, backgroundColor: dark ? '#222' : '#fff' }}>
<StatusBar style={dark ? 'light' : 'dark'} />
<Text style={{ color: dark ? '#fff' : '#222' }}>상태바 대비 실습</Text>
<Button title="배경 바꾸기" onPress={() => setDark(value => !value)} />
</SafeAreaView>
</SafeAreaProvider>
);
}
기대 결과: 버튼으로 밝은/어두운 배경을 바꿔도 상단 시스템 아이콘이 구분되어야 합니다. 여백은 SafeAreaView가 담당합니다.
자가 과제: dark 배경에서 style만 dark로 고정해 대비 차이를 확인하고 조건식으로 복원합니다. 웹에는 같은 네이티브 상태바가 없으므로 Android/iOS 결과를 별도로 기록합니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow Mobile은 할 일을 관리하는 모바일 앱입니다. 이 선택 실습에서는 기존 앱에 이번 기능을 이어서 적용합니다.
이번 주제: Expo Status Bar 사용법: Safe Area와 화면별 설정.
누적 프로젝트: TaskFlow Mobile
시작 위치: 아래 기존 환경·파일 구성에서 시작합니다. 이 글의 완료 ZIP은 결과 확인용입니다.
시작 체크포인트: Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법의 누적 파일까지 적용한 상태에서 이어갑니다.
npx expo install expo-status-bar 후 App.tsx를 갱신합니다. 밝은 화면의 dark 아이콘과 Safe Area 여백을 각각 확인합니다.
전체 파일·코드 보기
App.tsx — 이번 회차 전체 파일
import { StatusBar } from 'expo-status-bar';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { Text, View } from 'react-native';
type Task = { id: string; title: string; completed: boolean };
const tasks: Task[] = [{ id: '1', title: '첫 작업', completed: false }];
export default function App() {
return <SafeAreaProvider><SafeAreaView style={{ flex: 1 }}><View style={{ flex: 1, justifyContent: 'center', padding: 24 }}>
<StatusBar style="dark" /><Text>TaskFlow Mobile</Text>
{tasks.map(task => <Text key={task.id}>{task.title}</Text>)}
</View></SafeAreaView></SafeAreaProvider>;
}
다음 회차로 넘길 상태: 위 추가·수정 파일을 적용한 로컬 연습 폴더입니다. 실제 실행한 플랫폼과 결과를 기록하고 원래 독립 ZIP과 구분해 보관합니다.
아래 원래 과제의 확장 방향은 위 전체 파일이 제공하는 범위와 구분해 읽습니다. 아이콘 액션 확장·실제 세션 인증·background tracking은 추가 구현 과제입니다.
회차별 확장 질문과 확인 기준
독자 질문: 상단 여백은 맞는데 상태 표시줄 아이콘이 안 보일 때 어디를 바꾸나요?
앱에서 따라 하기
- 2회차 App.tsx의 작업 목록과 Safe Area 구조를 유지하고 expo-status-bar의 StatusBar를 화면에 추가합니다.
- 밝은 TaskFlow 배경에 style=”dark”를 적용한 뒤 style=”light”와 대비를 비교합니다.
- Safe Area 간격은 그대로 두고 상단 시스템 아이콘의 가독성만 확인합니다.
기기·빌드별 확인
예상 확인: Android Expo Go와 iOS는 시스템 막대 동작이 다를 수 있고 웹에는 같은 상태 표시줄이 없습니다. 이 단계에 Expo Router 설치는 필요하지 않습니다.
실행 확인 범위: 위의 예상 동작은 직접 따라 할 때 확인할 기준입니다. Android/iOS 기기·에뮬레이터, 웹 브라우저, development build, EAS 원격 빌드와 APK 설치의 실제 결과는 미확인입니다. 환경별로 실행한 뒤 화면, 오류 로그, 빌드 상태를 각각 확인하세요.
오류 점검과 다음 학습
아이콘 색과 배경 대비를 먼저 보고, 여백 문제라면 StatusBar 대신 Safe Area 설정을 확인합니다. Android edge-to-edge에서는 오래된 배경색 예제를 그대로 적용하지 않습니다.
글 소개·학습 준비·전체 목차
학습 목표·선수 지식: 상태바 글자색과 안전 영역을 구분하고 현재 활성 화면에 맞춰 색상을 바꿉니다. 이 회차는 Safe Area를 선수 학습으로 삼으며 Expo Router는 5회차에서 다룹니다.
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 13개 · 필수 9개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.