Tabs#
콘텐츠를 탭으로 구분하여 표시하는 컴포넌트입니다.
Live Preview#
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 관련된 콘텐츠를 여러 패널로 나누어 전환할 때
- 같은 맥락의 다른 뷰를 제공할 때 (개요/상세/리뷰 등)
- 페이지 이동 없이 콘텐츠를 전환할 때
대신 다른 컴포넌트를 사용하세요:
Accordion: 모든 섹션을 한 페이지에서 펼치고 접을 때Navigation: 앱의 주요 페이지 간 이동일 때Select: 옵션 선택이 목적이고 관련 콘텐츠 패널이 없을 때
기본 사용법 (Basic Usage)#
Tabs(
onChanged: handleTabChange,
tabs: [
Tab(label: '개요'),
Tab(label: '리뷰'),
Tab(label: '관련 상품'),
],
children: [
OverviewPanel(),
ReviewPanel(),
RelatedProductsPanel(),
],
)
// 컨트롤러 사용
final tabController = TabController(length: 3);
Tabs(
controller: tabController,
tabs: [
Tab(label: '탭 1'),
Tab(label: '탭 2'),
Tab(label: '탭 3'),
],
children: [Tab1Content(), Tab2Content(), Tab3Content()],
)
Tabs(
onChanged: handleTabChange,
tabs: [
Tab(label: '개요'),
Tab(label: '리뷰'),
Tab(label: '관련 상품'),
],
children: [
OverviewPanel(),
ReviewPanel(),
RelatedProductsPanel(),
],
)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
tabs | List<Tab> | 필수 | 탭 목록 |
children | List<Widget> | 필수 | 탭 콘텐츠 |
controller |
TabController? |
null |
탭 컨트롤러 |
onChanged |
CoreValueChanged<int>? |
null |
탭 변경 콜백 |
variant |
TabVariant |
line |
탭 스타일 변형 |
initialIndex | int | 0 | 초기 선택 탭 |
tabsStyle |
CoreTabsStyle<Color>? / CoreTabsStyle<CoreColor>? |
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
Tabs 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreTabsStyle<Clr> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 시맨틱 enum (variant) 과 behaviour (tabs
/ index / onChanged / swipeable) 는 위젯/컴포넌트 파라미터로 직접 전달합니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (
tabs,index,onChanged,variant,swipeable,padding) -
chrome / dimensional / 슬롯 스타일:
CoreTabsStyle한 곳으로 (activeIndicatorColor/activeIndicatorThickness/activeIndicatorRadius/tabBarBackgroundColor/tabBarPaddingH/tabBarPaddingV/tabGap/tabButtonStyle/contentTextStyle)
Resolve chain#
design system default for tabs
→ CoreTabsTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.tabsStyle // 인스턴스별
각 nested 슬롯 스타일 (tabButtonStyle / contentTextStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.
슬롯 매핑 (CoreTabsStyle 9 필드)#
| 필드 | 타입 (Flutter) | 적용 영역 |
|---|---|---|
activeIndicatorColor | Color? | 활성 탭 인디케이터 (밑줄/필) 색 |
activeIndicatorThickness | double? | 인디케이터 두께 (px) |
activeIndicatorRadius | double? | 인디케이터 모서리 반지름 (px) |
tabBarBackgroundColor | Color? | 탭 바 배경 색 |
tabBarPaddingH | double? | 탭 좌/우 패딩 (px) |
tabBarPaddingV | double? | 탭 위/아래 패딩 (px) |
tabGap | double? | 탭 버튼 간격 (px) |
tabButtonStyle |
CoreButtonStyle<Color>? |
탭 버튼 chrome (현재 구현은 foregroundColor / labelStyle 적용;
CoButton(variant: ghost)
asChild 통합은 #1322 이월)
|
contentTextStyle |
CoreTextStyle<Color>? |
탭 콘텐츠 텍스트 스타일 |
Migration#
기존 TabsTheme 의 평면 chrome (backgroundColor / borderRadius / containerPadding
/ tabPadding / activeColor / inactiveColor) 은 호환을 위해 유지되지만, 새 코드는
tabsStyle 슬롯을 사용하세요:
| 기존 (legacy theme 필드) | 새 위치 |
|---|---|
TabsTheme.backgroundColor |
tabsStyle.tabBarBackgroundColor
또는
CoreTabsTheme.style.tabBarBackgroundColor
|
TabsTheme.activeColor | tabsStyle.activeIndicatorColor |
TabsTheme.containerPadding (vertical) |
tabsStyle.tabBarPaddingV |
TabsTheme.containerPadding (horizontal) |
tabsStyle.tabBarPaddingH |
| 인스턴스 탭 텍스트 스타일 (불가능) | tabsStyle.tabButtonStyle.labelStyle |
| 인스턴스 콘텐츠 텍스트 스타일 (불가능) | tabsStyle.contentTextStyle |
사용 예 (Flutter)#
Tabs(
index: currentTabIndex,
onChanged: (index) => setState(() => currentTabIndex = index),
labels: ['Account', 'Password'],
tabsStyle: CoreTabsStyle(
activeIndicatorColor: Colors.blue,
activeIndicatorRadius: 8,
tabBarBackgroundColor: Color(0xFFF7F7F7),
tabBarPaddingH: 12,
tabBarPaddingV: 6,
tabGap: 4,
),
)
사용 예 (Web)#
TabList(
index: currentTabIndex,
onChanged: handleChange,
labels: ['Account', 'Password'],
tabsStyle: CoreTabsStyle<CoreColor>(
activeIndicatorThickness: 2,
tabBarPaddingH: 16,
tabBarPaddingV: 8,
),
)
변형 (Variants)#
라인 탭#
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
박스 탭#
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
필 탭#
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
TabPane(
index: 0,
labels: ['Account', 'Password'],
children: [
text('Account settings'),
text('Password settings'),
],
)
아이콘 포함#
Tab(
label: '설정',
icon: Icon(Icons.settings),
)
비활성화 탭#
Tab(label: '준비 중', enabled: false)
동작 스펙 (Behavior)#
탭 전환#
- 탭 클릭 시 해당 콘텐츠 패널이 즉시 표시
- 이전 패널은 숨겨지고 선택된 패널만 렌더링
onChanged콜백으로 탭 전환 이벤트 감지
TabPane — 고급 탭#
드래그 앤 드롭으로 탭 순서를 변경할 수 있는 고급 탭 컨테이너입니다.
TabPane<MyData>(
data: tabItems,
selectedIndex: currentIndex,
onFocusChanged: handleTabChange,
onSorted: handleReorder,
builder: (data) => TabChild(
tab: Text(data.label),
child: data.content,
),
)
- 탭이 많아지면 자동으로 스크롤 가능 (fade edge 표시)
leading,trailing위젯으로 탭 바에 추가 컨트롤 배치
테마 설정#
TabsTheme(
backgroundColor: Colors.grey[100],
activeColor: Colors.blue,
inactiveColor: Colors.grey,
expand: true, // 탭이 전체 너비를 채움
)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
탭 레이블은 짧고 명확하게 작성하세요.
Tabs(
tabs: [
Tab(label: '개요'),
Tab(label: '리뷰'),
Tab(label: '사양'),
],
children: [overviewPanel, reviewPanel, specPanel],
)
한두 단어로 각 탭의 내용을 명확히 전달합니다.
❌ Don't#
탭 레이블이 너무 길거나 모호하지 않게 하세요.
Tabs(
tabs: [
Tab(label: '제품의 전반적인 개요 및 소개'),
Tab(label: '사용자 리뷰 및 평가'),
],
children: [overviewPanel, reviewPanel],
)
긴 레이블은 탭 바 공간을 차지하고 스캔하기 어렵습니다.
✅ Do#
관련 콘텐츠끼리 탭으로 묶으세요.
Tabs(
tabs: [
Tab(label: '기본 정보'),
Tab(label: '보안 설정'),
Tab(label: '알림 설정'),
],
children: [profileForm, securityForm, notificationForm],
)
같은 맥락(설정)의 하위 카테고리를 탭으로 나누면 자연스럽습니다.
❌ Don't#
관계없는 콘텐츠를 탭으로 묶지 마세요.
Tabs(
tabs: [
Tab(label: '프로필'),
Tab(label: '결제 내역'),
Tab(label: '고객센터'),
],
children: [profile, payments, support],
)
독립된 기능은 별도 페이지(Navigation)로 분리하세요.
✅ Do#
비활성화 탭은 이유를 알려주세요.
Tab(
label: '분석 (프리미엄)',
enabled: false,
icon: Icon(Icons.lock),
)
잠금 아이콘으로 업그레이드가 필요함을 시각적으로 전달합니다.
❌ Don't#
설명 없이 탭을 비활성화하지 마세요.
Tab(label: '분석', enabled: false)
왜 접근할 수 없는지 알 수 없어 사용자가 혼란스럽습니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | 탭 목록으로 포커스 이동 |
← / → | 이전/다음 탭으로 포커스 이동 |
Home / End | 첫/마지막 탭으로 포커스 이동 |
Enter / Space | 포커스된 탭 선택 |
스크린 리더#
- Flutter: 탭 역할과 현재 선택 상태가 자동 전달. "탭 2/5, 선택됨" 형태로 읽힘
-
Web:
role="tab",role="tabpanel",aria-selected,data-state적용
ARIA 속성#
<div role="tablist">
<button role="tab" aria-selected="true" data-state="active">탭 1</button>
<button role="tab" aria-selected="false" data-state="inactive">탭 2</button>
</div>
<div role="tabpanel" data-state="active">콘텐츠 1</div>
크로스 플랫폼 차이점 (Platform Differences)#
v3.0부터 기본 API (
enabled,onChanged등)가 통일되었습니다. 아래는 플랫폼 고유 차이점만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| 선택 모델 | 인덱스 기반 (int) | 값 기반 (String) 또는 인덱스 |
| 탭 컨테이너 | Tabs, TabList, TabPane |
Tabs + TabsList + TabsTrigger + TabsContent |
| 탭 정의 | TabChild(tab: widget, child: content) |
별도 TabsTrigger와 TabsContent |
| 드래그 정렬 | TabPane에서 지원 | 없음 |
| 스크롤 가능 | TabPane 오버플로우 시 fade edge | 없음 |
| 테마 | TabsTheme, TabPaneTheme | Tailwind CSS 클래스 |
| 변형 | 암묵적 (테마 스타일링) | 클래스 기반 (line, boxed) |
| 확장 | TabsTheme.expand | flex CSS 클래스 |
| 아이콘 | Widget으로 자유 배치 | 텍스트 기반 (래핑 가능) |
관련 컴포넌트 (Related Components)#
- Accordion: 접고 펼 수 있는 콘텐츠 섹션. 모든 섹션을 동시에 볼 수 있어야 할 때 적합
- Navigation: 앱의 주요 섹션 간 이동. 탭과 달리 URL이 변경됨
조합 예제#
// 제품 상세 페이지 패턴
Card(
header: CardHeader(title: product.name),
body: CardBody(
child: Tabs(
tabs: [
Tab(label: '설명', icon: Icon(Icons.description)),
Tab(label: '사양', icon: Icon(Icons.list)),
Tab(label: '리뷰', icon: Icon(Icons.star)),
],
children: [
DescriptionPanel(product: product),
SpecificationsPanel(product: product),
ReviewsPanel(productId: product.id),
],
),
),
)