Scaffold#
Scaffold는 화면의 기본 구조를 잡는 통합 레이아웃 컴포넌트입니다. 위로 쌓이는 헤더 섹션, 중앙 본문, 아래로 쌓이는 푸터 섹션을 제공하며, 선택적으로 사이드바, 플로팅 액션 버튼, 로딩 진행 인디케이터를 함께 표시할 수 있습니다. 헤더 영역은
AppBar로 leading / 중앙 / trailing 클러스터를 손쉽게 구성합니다.
Live Preview#
class ScaffoldDefaultExample extends StatelessComponent {
const ScaffoldDefaultExample({super.key});
@override
Component build(BuildContext context) {
// Fixed-height frame (matches the Flutter preview canvas height)
// so the scaffold's `min-h-full` has a definite parent to fill —
// the Flutter twin fills its canvas constraints the same way.
return div(
[
Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
// `flex-1` fills the scaffold's body column (Flutter
// `Center` fills the available body the same way).
child: div(
[const Text('Main content area')],
classes: 'flex items-center justify-center flex-1',
),
),
],
classes: 'w-full',
styles: Styles(raw: const {'height': '${400 / 16}rem'}),
);
}
}
class ScaffoldDefaultExample extends StatelessWidget {
const ScaffoldDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
child: const Center(child: Text('Main content area')),
);
}
}
class ScaffoldChainExample extends StatelessComponent {
const ScaffoldChainExample({super.key});
@override
Component build(BuildContext context) {
// Fixed-height frame (matches the Flutter preview canvas height)
// so the scaffold's `min-h-full` has a definite parent to fill —
// the Flutter twin fills its canvas constraints the same way.
return div(
[
Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
// `flex-1` fills the scaffold's body column (Flutter
// `Center` fills the available body the same way).
child: div(
[const Text('Main content area')],
classes: 'flex items-center justify-center flex-1',
),
)
.withStyle(
const CoreScaffoldStyle(
headerBorderColor: CoreColor.token(CoreColors.outline),
headerBorderWidth: CoreStrokeWidth.stroke2,
),
)
.surfaceContainer,
],
classes: 'w-full',
styles: Styles(raw: const {'height': '${400 / 16}rem'}),
);
}
}
class ScaffoldChainExample extends StatelessWidget {
const ScaffoldChainExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
child: const Center(child: Text('Main content area')),
)
.withStyle(
const CoreScaffoldStyle(
headerBorderColor: CoreColor.token(CoreColors.outline),
headerBorderWidth: CoreStrokeWidth.stroke2,
),
)
.surfaceContainer;
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 헤더 / 본문 / 푸터로 구성된 일관된 화면 골격이 필요할 때
- 본문 옆에 사이드바를 두는 데스크톱 레이아웃
- 본문 위에 떠 있는 플로팅 액션 버튼이나 상단 로딩 인디케이터가 필요할 때
대신 다른 컴포넌트를 사용하세요:
Card: 화면 일부의 콘텐츠 박스가 필요할 때NavigationBar: 헤더가 아닌 주요 섹션 이동 네비게이션이 필요할 때
기본 사용법 (Basic Usage)#
Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
child: const Center(child: Text('Main content area')),
)
Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
child: div(
[const Text('Main content area')],
classes: 'flex items-center justify-center',
),
)
빠른 오버라이드 (Chain)#
이미 만든 Scaffold 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.surfaceContainer처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(surfaceContainer ==
CoreColors.surfaceContainer) 어느 컴포넌트에서 써도 뜻이 갈리지 않으며, 위 예시처럼 withStyle 뒤에 이어붙일 수도 있습니다.
class ScaffoldChainExample extends StatelessWidget {
const ScaffoldChainExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
child: const Center(child: Text('Main content area')),
)
.withStyle(
const CoreScaffoldStyle(
headerBorderColor: CoreColor.token(CoreColors.outline),
headerBorderWidth: CoreStrokeWidth.stroke2,
),
)
.surfaceContainer;
}
}
class ScaffoldChainExample extends StatelessComponent {
const ScaffoldChainExample({super.key});
@override
Component build(BuildContext context) {
// Fixed-height frame (matches the Flutter preview canvas height)
// so the scaffold's `min-h-full` has a definite parent to fill —
// the Flutter twin fills its canvas constraints the same way.
return div(
[
Scaffold(
headers: [
AppBar(
title: const Text('Dashboard').titleMedium,
trailing: const [Icon(LucideIcons.settings)],
),
],
footers: [
AppBar(
title: const Text('Ready').bodySmall.onSurfaceVariant,
),
],
// `flex-1` fills the scaffold's body column (Flutter
// `Center` fills the available body the same way).
child: div(
[const Text('Main content area')],
classes: 'flex items-center justify-center flex-1',
),
)
.withStyle(
const CoreScaffoldStyle(
headerBorderColor: CoreColor.token(CoreColors.outline),
headerBorderWidth: CoreStrokeWidth.stroke2,
),
)
.surfaceContainer,
],
classes: 'w-full',
styles: Styles(raw: const {'height': '${400 / 16}rem'}),
);
}
}
Props / Parameters#
Scaffold#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child |
Widget / Component |
필수 | 메인 본문 콘텐츠 |
headers |
List<Widget> / List<Component> |
[] |
본문 위에 쌓이는 헤더 섹션 |
footers |
List<Widget> / List<Component> |
[] |
본문 아래에 쌓이는 푸터 섹션 |
sidebar |
Widget? / Component? |
null |
본문 옆 사이드바 |
floatingActionButton |
Widget? / Component? |
null |
본문 위에 떠 있는 액션 버튼 |
floatingActionButtonLocation |
CoreScaffoldFabLocation |
endFloat |
FAB 위치 (endFloat / centerFloat / startFloat) |
floatingHeader |
bool |
false |
헤더가 본문 위에 떠서 레이아웃 공간을 차지하지 않음 |
floatingFooter |
bool |
false |
푸터가 본문 위에 떠서 레이아웃 공간을 차지하지 않음 |
loadingProgress |
double? |
null |
로딩 진행 값 (0.0–1.0) |
loadingProgressIndeterminate |
bool |
false |
무한 로딩 모드 |
resizeToAvoidBottomInset |
bool (Flutter 전용) |
true |
온스크린 키보드(IME)가 뜨면 콘텐츠를 그 높이만큼 리사이즈 (아래 동작 스펙 참조) |
variant |
CoreScaffoldVariant |
standard |
시각 변형 |
scaffoldStyle |
CoreScaffoldStyle? |
null |
크롬 / 치수 스타일 오버라이드 |
AppBar#
headers / footers 에 넣는 AppBar 는 자기 chrome(appBarStyle)과 variant 를 가진 별도 컴포넌트입니다 — 파라미터 표와 스타일 필드는
AppBar 페이지를 참고하세요.
스타일 시스템 (Style System)#
CoreScaffoldStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Background colour of the scaffold body surface. |
headerBackgroundColor |
CoreColor? |
Background colour of the header section. |
footerBackgroundColor |
CoreColor? |
Background colour of the footer section. |
headerBorderColor |
CoreColor? |
Colour of the header's bottom border. |
headerBorderWidth |
double? |
Thickness of the header's bottom border (logical px). |
headerPadding |
CoreEdgeInsets? |
Deprecated (since 0.131) — use
CoreAppBarStyle.padding
. Outer padding of the header / app-bar section.
|
headerSpacing |
double? |
Deprecated (since 0.131) — use
CoreAppBarStyle.spacing
. Spacing between the header's leading / centre / trailing clusters (logical px). Mirrors Flutter
Row.spacing
between leading, centre and trailing children.
|
headerActionSpacing |
double? |
Deprecated (since 0.131) — use
CoreAppBarStyle.actionSpacing
. Spacing between adjacent widgets inside the header's leading or trailing cluster (logical px). Mirrors Flutter
Row.spacing
applied to each action cluster.
|
fabPadding |
CoreEdgeInsets? |
Padding around a floating action button. |
CoreScaffoldStyle 변형별 기본값 (CoreScaffoldVariantStyle)#
| 필드 | standard |
|---|---|
backgroundColor | surface |
headerBackgroundColor | surface |
footerBackgroundColor | surface |
동작 스펙 (Behavior)#
레이아웃#
- 고정 모드: 헤더 / 푸터가 레이아웃 공간을 차지하며 본문을 밀어냄
-
플로팅 모드:
floatingHeader/floatingFooter가true면 본문 위에 겹쳐 표시 - 사이드바:
sidebar를 지정하면 본문과 가로로 나란히 배치
로딩 인디케이터#
loadingProgress를 지정하면 화면 최상단에 진행 막대 표시loadingProgressIndeterminate가true면 무한 로딩 막대 표시
키보드(IME) 인셋 회피 — resizeToAvoidBottomInset (Flutter 전용)#
-
true면 헤더 / 본문 / 푸터 스택(플로팅 오버레이·FAB 포함)을MediaQuery.viewInsets.bottom만큼 하단 패딩 — 채팅 입력바·폼 같은 하단 고정 콘텐츠가 소프트 키보드와 IME 액세서리 스트립(물리 키보드의 자동완성 툴바 등) 위로 올라옵니다. MaterialScaffold.resizeToAvoidBottomInset과 동일한 의미입니다. - 소비한 인셋은 자식
MediaQuery에서 제거되어 중첩 Scaffold가 이중 적용하지 않습니다. -
기본값은
true— MaterialScaffold의 기본값과 같고, Web 쌍둥이와도 같습니다. 브라우저는 IME 에 맞춰 visual viewport 를 리사이즈하며 이를 끌 방법이 없으므로, Flutter 만false이면 같은 코드가 Web 에서는 키보드 위로 올라오고 Flutter 에서는 가려집니다. - 콘텐츠가 키보드 뒤로 이어져야 하는 화면(전면 미디어·지도)에서는
false로 끕니다. -
본문 높이가 줄어들므로 스크롤 불가한 고정 높이 본문은
BOTTOM OVERFLOWED를 냅니다 — 종전에 "가려짐"이던 것이 경고로 드러난 것이고, 본문을 스크롤 가능하게 만드는 것이 해법입니다. -
Web에는 이 파라미터가 없습니다. 브라우저가 IME를 문서 레벨에서 native 처리하기 때문입니다 — 모바일 웹에서 키보드 회피가 필요하면 앱 진입 HTML의 viewport meta에
interactive-widget=resizes-content를 지정하거나 CSS100dvh단위를 사용하세요. 이는 Scaffold 인스턴스가 아니라 문서(document) 단위 설정입니다.
사용 가이드라인 (Usage Guidelines)#
✅ Do#
키보드 뒤로 이어져야 하는 화면에서만 resizeToAvoidBottomInset 끄기
Scaffold(
resizeToAvoidBottomInset: false,
child: fullBleedMapView,
)
기본값이 true라 채팅 입력바·폼처럼 하단에 고정된 콘텐츠는 별도 설정 없이 소프트 키보드 위로 올라옵니다 — Material Scaffold 및 Web 쌍둥이와 같은 동작입니다. 끄는 것은 전면 미디어·지도처럼 콘텐츠가 키보드 뒤까지 이어져야 하는 화면뿐입니다. Web에는 이 파라미터가 없으므로 모바일 웹 키보드 회피는 viewport meta interactive-widget=resizes-content 또는 100dvh로 문서 단위에서 처리합니다.
❌ Don't#
floatingHeader를 켠 채 본문에 상단 여백을 주지 않기
// ❌ floatingHeader가 레이아웃 공간을 차지하지 않아 헤더가 본문 상단과 겹침
Scaffold(
floatingHeader: true,
headers: [appBar],
child: contentWithNoTopPadding,
)
floatingHeader: true면 헤더가 본문 위에 겹쳐 표시될 뿐 레이아웃 공간을 차지하지 않습니다. 본문 콘텐츠가 헤더 아래로 가려지지 않으려면 본문 쪽에 헤더 높이만큼의 여백을 직접 줘야 합니다.
접근성 (Accessibility)#
스크린 리더#
- Flutter: 본문을
ColoredBox로 감싸 레이아웃만 담당 — 헤더 / 푸터의 시맨틱은 각 슬롯 위젯이 담당 - Web: 루트
<div role="group" aria-label="Scaffold">적용
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | Scaffold / AppBar |
Scaffold / AppBar |
| 렌더 루트 | ColoredBox + Column / Stack |
<div> (flex column, position: relative) |
| 플로팅 헤더 | Stack + Position 오버레이 |
position: sticky |
| FAB 위치 | Align + Padding |
position: absolute |
| 키보드(IME) 회피 | 기본 동작 — resizeToAvoidBottomInset: false 로 옵트아웃 |
파라미터 없음 — 브라우저 native (viewport meta
interactive-widget=resizes-content
/
100dvh
)
|
관련 컴포넌트 (Related Components)#
- NavigationBar: 주요 섹션 이동 네비게이션 컨테이너
- Card: 화면 일부의 콘텐츠 박스