핵심 요약
Material 3 신규 화면은 NavigationBar를 우선 검토하고, 탭 전환 시 상태를 유지할지 다시 만들지를 화면 요구사항에 따라 정해야 합니다.
검수 범위
탭 개수의 절대 규칙보다 목적지 구조, 상태 유지, 화면 크기별 대안을 공식 API 기준으로 검수했습니다.
BottomNavigationBar 기본 이해
하단 탭 역할
Flutter에서 BottomNavigationBar는 화면 아래쪽에 놓는 탭 메뉴입니다. 보통 홈, 검색, 알림, 마이페이지처럼 앱의 대표 화면을 빠르게 바꿀 때 사용합니다. 공식 문서에서도 이 위젯은 보통 3개에서 5개 정도의 주요 화면 이동에 쓰고, Scaffold의 bottomNavigationBar 자리에 넣어 사용한다고 안내합니다. 새 앱이거나 Material 3를 쓰는 앱이라면 NavigationBar가 더 권장됩니다.
언제 쓰면 좋은지
하단 탭은 앱 안에서 자주 오가는 큰 메뉴가 몇 개 정해져 있을 때 잘 맞습니다. 반대로 화면이 넓은 태블릿이나 웹처럼 가로 폭이 큰 경우에는 하단 탭보다 NavigationRail 같은 방식이 더 어울릴 수 있습니다.
준비 코드 작성
가장 쉬운 예제
아래 예제는 BottomNavigationBar를 처음 붙일 때 가장 많이 쓰는 형태입니다. 현재 선택한 탭 번호를 currentIndex로 저장해두고, 탭을 누를 때마다 setState()로 숫자만 바꾸면 됩니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const MainTabPage(),
);
}
}
class MainTabPage extends StatefulWidget {
const MainTabPage({super.key});
@override
State createState() => _MainTabPageState();
}
class _MainTabPageState extends State {
int _currentIndex = 0;
final List _pages = const [
Center(child: Text('홈 화면')),
Center(child: Text('검색 화면')),
Center(child: Text('마이페이지 화면')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar 예제'),
),
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '홈',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '검색',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '마이',
),
],
),
);
}
}
코드에서 꼭 봐야 할 부분
BottomNavigationBar는 items를 최소 2개 이상 넣어야 하고, 각 항목에는 아이콘과 라벨이 필요합니다. 또 현재 선택된 탭을 보여주려면 currentIndex가 꼭 있어야 합니다. 탭을 눌렀을 때 onTap 안에서 선택 번호를 바꿔주지 않으면 화면이 바뀌지 않습니다.
핵심 속성 정리
currentIndex 의미
currentIndex는 지금 선택된 탭 번호입니다. 첫 번째 탭이면 0, 두 번째 탭이면 1입니다. 이 값이 바뀌면 BottomNavigationBar도 선택 상태를 같이 바꿔서 보여줍니다. 공식 예제도 같은 방식으로 selectedIndex 또는 currentIndex를 바꿔 화면을 전환합니다.
onTap 사용법
onTap은 사용자가 탭을 눌렀을 때 실행됩니다. 여기서 받은 index를 상태값에 저장하면 됩니다. 가장 많이 쓰는 코드는 아래 한 줄입니다.
onTap: (index) {
setState(() {
_currentIndex = index;
});
}
items 작성법
각 탭은 BottomNavigationBarItem으로 만듭니다. 아이콘만 넣기보다 아이콘과 글자를 함께 넣는 경우가 많습니다. 예전에는 title을 쓰던 코드도 있었지만, 공식 문서 기준으로 지금은 label을 사용합니다.
화면 유지 방법
왜 상태가 초기화되는지
처음 예제처럼 body: _pages[_currentIndex] 방식으로 바꾸면 탭을 옮길 때 이전 화면이 다시 만들어질 수 있습니다. 그래서 스크롤 위치, 입력값, 체크 상태가 초기화되는 경우가 자주 나옵니다.
IndexedStack 적용 방법
탭마다 입력값이나 스크롤 위치를 살리고 싶다면 IndexedStack을 쓰는 편이 좋습니다. 공식 문서에서도 IndexedStack은 여러 자식 중 하나만 보이게 하면서 각 자식의 상태를 유지하는 예제를 제공합니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const MainTabPage(),
);
}
}
class MainTabPage extends StatefulWidget {
const MainTabPage({super.key});
@override
State createState() => _MainTabPageState();
}
class _MainTabPageState extends State {
int _currentIndex = 0;
final List _pages = const [
HomePage(),
SearchPage(),
MyPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('하단 탭 상태 유지 예제'),
),
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '홈',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '검색',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '마이',
),
],
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('홈 화면'));
}
}
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('검색 화면'));
}
}
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('마이페이지 화면'));
}
}
탭 개수별 차이 정리
3개 이하일 때
BottomNavigationBar는 항목 수가 4개보다 적으면 기본적으로 fixed 방식으로 동작합니다. 선택된 탭과 선택되지 않은 탭이 비교적 일정한 형태로 보입니다.
4개 이상일 때
항목이 4개 이상이면 기본값이 shifting으로 바뀝니다. 이때는 선택된 항목의 배경색이 더 크게 반영될 수 있어서 디자인이 예상과 다르게 보일 수 있습니다. 탭이 많아질수록 화면이 답답해지기 쉬우니 처음에는 3개나 4개 정도로 시작하는 편이 무난합니다.
자주 하는 실수 정리
StatelessWidget으로 시작하는 경우
탭을 누를 때마다 화면이 바뀌려면 선택된 번호가 바뀌어야 합니다. 그래서 보통은 StatefulWidget으로 만들어야 편합니다. 상태값을 저장하고 setState()로 다시 그려야 하기 때문입니다.
currentIndex를 빼먹는 경우
아이콘은 보이는데 선택 표시가 안 맞거나 처음 탭 상태가 이상하게 보이는 경우가 많습니다. 이런 때는 currentIndex를 넣지 않았거나, 바뀌는 값이 잘못 연결된 경우가 많습니다.
items 개수와 화면 개수가 다른 경우
탭은 3개인데 _pages는 2개만 넣으면 눌렀을 때 인덱스 오류가 날 수 있습니다. 탭 개수와 보여줄 화면 개수는 꼭 맞춰야 합니다.
상태 유지가 안 되는 경우
검색창 입력값, 리스트 스크롤 위치가 탭 이동 뒤 사라지면 IndexedStack을 먼저 떠올리면 됩니다. 탭을 옮길 때도 각 화면을 그대로 들고 있기 때문에 이런 문제를 줄이기 좋습니다.
디자인 꾸미기 방법
기본 색상 변경
선택된 아이콘 색, 선택되지 않은 아이콘 색, 배경색은 BottomNavigationBar 속성으로 쉽게 바꿀 수 있습니다.
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
backgroundColor: Colors.white,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '홈'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: '검색'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '마이'),
],
),
글자 표시 방식
탭 글자를 항상 보이게 할지, 선택된 탭만 보이게 할지도 디자인에 따라 바꿀 수 있습니다. 다만 처음 배우는 단계에서는 기본값으로 두고, 먼저 탭 전환이 제대로 되는지부터 확인하는 편이 실수가 적습니다.
NavigationBar 같이 알아두기
지금 기준에서 더 많이 권장되는 위젯
Flutter 공식 문서에서는 BottomNavigationBar보다 NavigationBar를 새 앱과 Material 3 환경에서 더 권장합니다. 역할은 비슷하지만 이름과 속성이 조금 다릅니다. BottomNavigationBar의 items 대신 destinations를 쓰고, currentIndex 대신 selectedIndex, onTap 대신 onDestinationSelected를 사용합니다.
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: '홈',
),
NavigationDestination(
icon: Icon(Icons.search),
label: '검색',
),
NavigationDestination(
icon: Icon(Icons.person),
label: '마이',
),
],
),
지금 무엇을 쓰면 좋은지
기존 강의나 예제를 따라가야 한다면 BottomNavigationBar로 시작해도 괜찮습니다. 다만 새 프로젝트를 처음 만드는 상황이라면 NavigationBar까지 같이 익혀두는 편이 좋습니다. 공식 문서도 그 방향을 안내하고 있습니다.
iOS까지 생각한 작성 방법
안드로이드와 아이폰을 다르게 보여주고 싶을 때
Flutter 공식 문서에서는 안드로이드에서는 NavigationBar, iOS에서는 CupertinoTabBar를 써서 플랫폼에 맞게 보이게 하는 방법도 소개합니다. 앱을 더 자연스럽게 보이게 하고 싶다면 나중에 이 방식까지 넓혀가면 됩니다.
마무리 정리
처음 만들 때 꼭 기억할 것
Flutter 하단 탭 만들기는 생각보다 단순합니다.
먼저 StatefulWidget으로 페이지를 만들고,currentIndex로 현재 탭 번호를 저장한 뒤,BottomNavigationBar를 Scaffold.bottomNavigationBar에 넣고,onTap에서 번호를 바꾸면 끝입니다.
실전에서 가장 많이 쓰는 팁
입문 단계에서는 먼저 기본 예제로 완성해보는 게 좋습니다. 그다음 탭 이동 후에도 입력값이나 스크롤 위치를 살리고 싶다면 IndexedStack을 붙이면 됩니다. 그리고 새 프로젝트라면 NavigationBar도 같이 익혀두면 나중에 다시 손볼 일이 줄어듭니다.
결론
Flutter에서 하단 탭을 만드는 방법은 처음에는 어려워 보여도 실제로는 몇 가지만 이해하면 충분히 구현할 수 있습니다. 핵심은 Scaffold에 bottomNavigationBar를 넣고, 현재 선택된 탭 번호를 currentIndex로 관리하면서 onTap으로 화면을 바꾸는 것입니다. 이 기본만 익혀도 홈, 검색, 마이페이지처럼 자주 쓰는 화면을 깔끔하게 나눌 수 있습니다.
특히 처음에는 BottomNavigationBar로 시작하는 것이 가장 이해하기 쉽습니다. 탭 개수에 맞춰 화면 리스트를 만들고, 눌렀을 때 해당 인덱스의 화면을 보여주도록 연결하면 기본 하단 탭은 바로 완성됩니다. 여기에 IndexedStack까지 같이 사용하면 탭을 옮겨도 입력값이나 스크롤 위치가 유지되어 훨씬 실용적인 앱 화면을 만들 수 있습니다.
또 하나 기억할 점은, 예전 예제에서는 BottomNavigationBar가 많이 보이지만 최근에는 NavigationBar도 자주 사용된다는 점입니다. 그래서 입문 단계에서는 BottomNavigationBar로 먼저 익히고, 이후 NavigationBar까지 넓혀서 보면 최신 방식까지 자연스럽게 이어갈 수 있습니다.
정리하면 Flutter 하단 탭은 어렵게 접근할 필요가 없습니다. 기본 예제로 먼저 직접 만들어보고, 그다음 상태 유지와 디자인 수정까지 하나씩 붙여가면 충분합니다. 처음 한 번 제대로 만들어보면 이후에는 대부분의 앱 화면에서 비슷한 방식으로 쉽게 응용할 수 있습니다.
검증 기준과 참고 자료
이 글은 2026-09-08에 아래 공식 문서와 공개 기술 문서를 기준으로 내용과 용어를 다시 검수했습니다. 제품 버전과 기기 제조사에 따라 화면 이름은 달라질 수 있으므로 실제 화면과 공식 문서를 함께 확인하세요.