PageHeader
페이지 머리 — 보조 설명 · 제목 · 설명 문장과 끝의 행동들
목차
PageHeader 는 페이지 맨 위의 머리입니다. 짧은 보조 설명(eyebrow) 아래 페이지
제목을, 그 아래 한 문장 설명을 쌓고, 행동(Button 들)을 줄 끝에 둡니다. 폭이
좁아지면 행동이 글 아래로 내려가고, 그래도 좁으면 행동끼리 줄을 바꿉니다 — 머리는
어떤 폭에서도 넘치지 않습니다.
기본 사용법 (Basic Usage)#
PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
description: const Text('Members, billing and integrations.'),
actions: [
Button(
variant: .secondary,
onPressed: () {},
child: const Text('Cancel'),
),
Button(onPressed: () {}, child: const Text('Save changes')),
],
)
PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
description: const Text('Members, billing and integrations.'),
actions: [
Button(
variant: .secondary,
onPressed: () {},
child: const Text('Cancel'),
),
Button(onPressed: () {}, child: const Text('Save changes')),
],
)
슬롯에 넘긴 Text 는 머리가 정한 역할 · 색으로 그려집니다 — 보조 설명 · 제목 · 설명의
글자 스타일을 따로 적지 않습니다. 폭은 부모가 정합니다 — 머리는 받은 폭을 채웁니다.
Live Preview#
Settings
코드 보기
class PageHeaderDefaultExample extends StatelessComponent {
const PageHeaderDefaultExample({super.key});
@override
Component build(BuildContext context) {
return PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
description: const Text('Members, billing and integrations.'),
actions: [
Button(
variant: .secondary,
onPressed: () {},
child: const Text('Cancel'),
),
Button(onPressed: () {}, child: const Text('Save changes')),
],
);
}
}
class PageHeaderDefaultExample extends StatelessWidget {
const PageHeaderDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
description: const Text('Members, billing and integrations.'),
actions: [
Button(
variant: .secondary,
onPressed: () {},
child: const Text('Cancel'),
),
Button(onPressed: () {}, child: const Text('Save changes')),
],
);
}
}
구조 (Anatomy)#
글 묶음(보조 설명 · 제목 · 설명)과 행동 묶음 두 칸입니다. 글 묶음이 행동이 남긴 폭을 모두 차지합니다.
| 부분 | 역할 | 슬롯 |
|---|---|---|
eyebrow | 제목 위의 짧은 보조 설명 — 섹션 이름이나 상위 경로 | eyebrow |
title |
페이지 제목 — headingLevel 단계의 머리글 |
title |
description | 제목 아래 한 문장 | description |
actions | 줄 끝의 행동들 — 보통 Button | actions |
Figma: ❖ PageHeader
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 설정 · 목록 · 대시보드 같은 페이지 본문 맨 위에 제목과 그 페이지의 행동을 둘 때
- 제목 위에 섹션 이름을, 아래에 한 문장 설명을 함께 보여 줄 때
대신 다른 컴포넌트를 사용하세요:
- PanelHeader: 페이지 안의 패널 · 카드 하나의 머리 — 제목과 끝의 작은 행동 하나
- AppBar: 화면 위에 고정되는 앱 바 — 뒤로 가기 · 메뉴 같은 탐색과 전역 행동
Props / Parameters#
W 는 Flutter의 Widget / Web의 Jaspr Component입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
eyebrow |
W? |
null |
제목 위의 짧은 보조 설명. 없으면 그 줄을 그리지 않습니다 |
title |
W |
필수 | 페이지 제목. headingLevel 단계의 머리글로 읽힙니다 |
description |
W? |
null |
제목 아래 한 문장. 없으면 그 줄을 그리지 않습니다 |
actions |
List<W> |
const [] |
줄 끝의 행동들. 비어 있으면 행동 묶음을 그리지 않습니다 |
headingLevel |
int |
CorePageHeaderContract.defaultHeadingLevel(1) |
제목의 머리글 단계 1–6. 페이지 제목은 보통 1 입니다 |
pageHeaderStyle |
CorePageHeaderStyle? |
null |
모든 chrome 이 지나는 단일 슬롯 (아래 표 참고) |
CorePageHeaderStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
eyebrowTextStyle |
CoreTextStyle? |
Ambient text style of the eyebrow slot, merged on top of [defaultEyebrowTextStyle]. |
titleTextStyle |
CoreTextStyle? |
Ambient text style of the title slot, merged on top of [defaultTitleTextStyle]. |
descriptionTextStyle |
CoreTextStyle? |
Ambient text style of the description slot, merged on top of [defaultDescriptionTextStyle]. |
textSpacing |
double? |
Space between the lines of the text block — eyebrow, title and description, whichever are present.
null
→ [defaultTextSpacing].
|
textActionsSpacing |
double? |
Space between the text block and the actions — across the row while they sit side by side, and down the column once the actions have moved under the text.
null
→ [defaultTextActionsSpacing].
|
actionSpacing |
double? |
Space between two actions, and between two lines of them when they wrap.
null
→ [defaultActionSpacing].
|
minTextWidth |
double? |
The narrowest the text block may get beside the actions. Below it the actions move under the text.
null
→ [defaultMinTextWidth].
|
스타일 시스템 (Style System)#
pageHeaderStyle#
- 시맨틱 / behaviour: 위젯 파라미터로 직접 (
headingLevel) -
chrome / dimensional:
CorePageHeaderStyle한 곳으로 (세 줄의 글자 스타일, 줄 사이 · 글과 행동 사이 · 행동 사이 간격, 글 묶음의 최소 폭)
| 부분 | 역할 | 색 |
|---|---|---|
eyebrow |
labelXl (15 · Medium) |
onSurfaceVariant |
title |
headlineMedium (28 · Bold) |
onSurface |
description |
bodyMedium (14) |
onSurfaceVariant |
간격은 줄 사이(textSpacing) 4, 글 묶음과 행동 사이(textActionsSpacing) 8, 행동
사이(actionSpacing) 8 입니다. 세 간격은 간격 사다리 위의 값이라 밀도(CoreDensity)가 한
칸 옮깁니다. 글 묶음의 최소 폭(minTextWidth) 240 은 간격이 아니라 폭이라 밀도에
움직이지 않습니다.
Resolve chain
CorePageHeaderStyle.defaultX // 기본값
→ CorePageHeaderTheme.style // 프로젝트 공통
→ widget.pageHeaderStyle // 인스턴스별
사용 예
CoreComponentTheme(
pageHeader: CorePageHeaderTheme(
style: CorePageHeaderStyle(
titleTextStyle: CoreTextStyle.token(CoreTextStyles.headlineSmall),
),
),
)
빠른 오버라이드 (Chain)
이미 만든 PageHeader 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle
체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만,
이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class PageHeaderChainExample extends StatelessWidget {
const PageHeaderChainExample({super.key});
@override
Widget build(BuildContext context) {
return PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
actions: [Button(onPressed: () {}, child: const Text('Save changes'))],
).withStyle(
const CorePageHeaderStyle(
titleTextStyle: CoreTextStyle.token(CoreTextStyles.headlineSmall),
textSpacing: CoreSpace.space8,
),
);
}
}
class PageHeaderChainExample extends StatelessComponent {
const PageHeaderChainExample({super.key});
@override
Component build(BuildContext context) {
return PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
actions: [Button(onPressed: () {}, child: const Text('Save changes'))],
).withStyle(
const CorePageHeaderStyle(
titleTextStyle: CoreTextStyle.token(CoreTextStyles.headlineSmall),
textSpacing: CoreSpace.space8,
),
);
}
}
동작 스펙 (Behavior)#
배치#
- 나란히: 행동 묶음이 자기 폭(가장 넓을 때의 폭)을 차지하고, 글 묶음이 남은 폭을 모두 차지합니다. 둘은 아래 끝이 맞습니다 — 행동이 마지막 줄(설명이 있으면 설명)과 같은 높이에 앉습니다
-
행동이 아래로: 글 묶음이 받을 폭이
minTextWidth(240)보다 좁아지면 행동 묶음이 글 아래로 내려가 시작 쪽에 붙습니다. 글 묶음과의 간격은 나란할 때와 같은textActionsSpacing입니다 -
행동끼리 줄 바꿈: 한 줄 전체로도 행동이 다 들어가지 않으면 행동끼리 줄을 바꿉니다
(
actionSpacing이 가로 · 세로 간격). 머리는 넘치지 않습니다 - 방향: 오른쪽에서 왼쪽으로 쓰는 로케일에서 두 배치가 함께 뒤집힙니다
-
폭이 정해지지 않은 자리: 가로 스크롤 안처럼 폭이 정해지지 않은 자리에서는 두 플랫폼 모두
글 묶음을 내용 폭으로 잽니다 — 글이
minTextWidth보다 짧으면 행동이 아래로 내려갑니다. 머리는 폭이 정해진 자리(페이지 열 · 패널)에 놓습니다
인터랙션#
- 머리 자체는 입력을 받지 않습니다 — 행동의 동작은 각 행동(
Button등)이 가집니다
상태 전환#
- 상태가 없습니다. 같은 슬롯과 같은 폭이면 같은 배치입니다
애니메이션#
- 없습니다. 폭이 바뀌어 행동이 내려가거나 올라올 때도 즉시 바뀝니다
사용 가이드라인 (Usage Guidelines)#
Do#
주 행동을 끝에 둔다
PageHeader(
title: const Text('Settings'),
actions: [
Button(variant: .secondary, onPressed: cancel, child: const Text('Cancel')),
Button(onPressed: save, child: const Text('Save changes')),
],
)
이유: 행동은 시작에서 끝 순서로 놓이고 탭 순서도 같습니다. 주 행동(primary)을 끝에 두면
나란할 때는 줄 끝에, 내려갔을 때는 마지막에 놓여 어느 배치에서도 같은 자리로 읽힙니다.
Don't#
패널 머리에 PageHeader 를 쓰지 않는다
// ❌ 페이지 안에 또 하나의 h1 이 생기고, 28 크기 제목이 패널을 누른다
Card(header: PageHeader(title: const Text('Recent files')))
// ✅ 패널 머리는 PanelHeader — 제목 18 · h2
Card(header: PanelHeader(title: const Text('Recent files')))
이유: 한 페이지에 머리글 1단계는 하나입니다. 패널은 그 아래 단계입니다.
접근성 (Accessibility)#
역할#
-
Flutter: 제목을
Semantics(headingLevel:)로 감쌉니다 — 웹에서는aria-level, Android 에서는isHeading으로 나갑니다.header: true는 쓰지 않습니다 — 이 킷에서header:는 페이지 배너 랜드마크의 몫이라, 머리글마다 켜면 머리글이 배너로도 읽힙니다 -
Web: 제목이
<h1>…<h6>요소(headingLevel)입니다. 브라우저 기본 여백은 지웁니다
키보드#
| 키 | Flutter | Web |
|---|---|---|
Tab | 행동들 사이를 시작 → 끝 순서로 | 행동들 사이를 시작 → 끝 순서로 |
머리 자체는 포커스를 받지 않습니다.
스크린 리더#
보조 설명 → 제목(머리글) → 설명 → 행동 순서로 읽힙니다. 제목만 머리글이고, 보조 설명과 설명은 일반 텍스트입니다. 상태 통보(live region)는 없습니다.
포커스 관리#
- 진입 · 이탈 · 트랩: 없음 — 머리는 오버레이가 아닙니다
알려진 제약#
- 머리글 단계는 호출자가 고릅니다. 페이지의 단계 구조(1 → 2 → 3)를 지키는 것은 호출자의 몫입니다
전역 축(동작 줄이기 · 고대비 · 색 강제 모드 · 시맨틱 역할 · 최소 터치 타겟)은 접근성 페이지가 한 곳에서 다룹니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | PageHeader | PageHeader |
| 배치 |
렌더 오브젝트(
HeaderWrapLayout
)가 행동 묶음의 폭을 재고, 글 묶음에 남는 폭이
minTextWidth
보다 좁으면 행동을 아래로 내립니다
|
flex-wrap
줄 — 글 묶음의
flex-basis
가
minTextWidth
, 하나의
gap
이 가로 · 세로 간격입니다
|
| 행동 줄 바꿈 | Wrap(spacing, runSpacing) |
flex-wrap + gap |
| 머리글 | Semantics(headingLevel:) |
<h1>…<h6> |
| 최소 폭의 단위 | 논리 px × 레이아웃 배율 | rem — 브라우저 글자 배율을 따릅니다 |
| 크기 질의 | IntrinsicWidth · IntrinsicHeight · Table 안에서도 답합니다 |
해당 없음 — 브라우저 배치 |
| Web 전용 HTML 표면 | — |
attributes
·
classes
·
css
·
eventHandlers
·
id
통과
|
위 차이 밖에는 없습니다 — 이름 · 순서 · 기본값을 두 위젯 생성자에서 대조했습니다.
관련 컴포넌트 (Related Components)#
- PanelHeader: 페이지 안 패널의 머리 — 같은 배치 규칙
- Button:
actions에 놓는 행동 - AppBar: 화면 위의 고정 바
- Scaffold: 머리가 놓이는 화면 구조
조합 예제#
Column(
crossAxisAlignment: .stretch,
spacing: CoreSpace.space24,
children: [
PageHeader(
eyebrow: const Text('Workspace'),
title: const Text('Settings'),
actions: [Button(onPressed: save, child: const Text('Save changes'))],
),
Card(
header: PanelHeader(
title: const Text('Members'),
action: Button(
variant: .ghost,
size: .sm,
onPressed: invite,
child: const Text('Invite'),
),
),
body: const Text('3 members'),
),
],
)