Hero
랜딩 히어로 블록. 가운데 정렬 또는 카피·미디어 분할.
목차
페이지 흐름 안의 히어로입니다. 배경색, 선택적 배경 이미지와 오버레이, 그리고 전경을 그립니다. 위에 떠 있는 오버레이가 아닙니다.
HeroSection 은 이 블록의 이전 이름입니다. 같은 크롬이고, layout 과
media 를 생략하면 예전처럼 카피가 가운데에 있습니다.
Live Preview#
가운데는 layout 을 생략한 기본입니다. 분할은 넓은 화면에서 카피가 앞이고
미디어가 뒤이며, 좁은 화면에서는 카피가 위, 미디어가 아래입니다.
코드 보기
class HeroSectionDefaultExample extends StatelessComponent {
const HeroSectionDefaultExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return div(
[
Hero(
heroStyle: const CoreHeroStyle(minHeight: 280),
child: div(
[
div(
[Text('Welcome to CoUI')],
classes:
'text-${CoreTextStyles.titleLarge.name} font-${CoreFontWeight.scale.semibold} text-${cs.onSurface}',
),
const Gap.space12(),
div(
[
Text(
'Cross-platform design system for Flutter and Jaspr.',
),
],
classes:
'text-${CoreTextStyles.bodyMedium.name} text-${cs.onSurfaceVariant} text-center',
),
],
classes: 'flex flex-col items-center',
),
),
],
// w-full 은 클래스로 — docs 의 폭 부여 룰(:has(.w-full))이
// 클래스만 매칭해서 inline width 로는 래퍼가 content-size 로 남는다.
classes: 'w-full',
styles: const Styles(raw: {'height': '280px'}),
);
}
}
class HeroSectionDefaultExample extends StatelessWidget {
const HeroSectionDefaultExample({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SizedBox(
height: 280,
child: Hero(
heroStyle: const CoreHeroStyle(minHeight: 280),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Welcome to CoUI',
style: theme.typography.titleLarge.toValue(theme: theme).copyWith(
color: theme.colorScheme.onSurface.toValue(),
fontWeight: FontWeight.w600,
),
),
const Gap.space12(),
Text(
'Cross-platform design system for Flutter and Jaspr.',
textAlign: TextAlign.center,
style: theme.typography.bodyMedium.toValue(theme: theme).copyWith(
color: theme.colorScheme.onSurfaceVariant.toValue(),
),
),
],
),
),
);
}
}
레이아웃#
축은 Layout=center | split 하나뿐입니다.
| 값 | 그리는 것 |
|---|---|
center | [child] 를 가운데에 둡니다. [media] 는 그리지 않습니다. |
split |
[child] (카피)와 [media] 를 한 쌍으로 둡니다. 미디어는 가로 배치에서 뒤쪽입니다. [media] 가 없으면 카피만 가운데에 둡니다. |
배경 [imageUrl] 과 오버레이는 두 레이아웃 모두 히어로 뒤에 있습니다. [media] 는 전경이고, 배경 이미지를 대신하지 않습니다.
반응형#
분할은 ResponsiveRowColumn 과 같은 기준에서 접힙니다.
[CoreResponsiveRowColumnContract.defaultSwitchAt] 은 태블릿이고, 그 값은
[CoreBreakpoint.md] (768px) 입니다.
- 768px 미만: 세로. 카피가 위, 미디어가 아래.
- 768px 이상: 가로. 카피가 앞, 미디어가 뒤.
Flutter 는 MediaQuery 로, Web 은 같은 뷰포트 폭으로 이 기준을 읽습니다.
창 크기가 바뀌면 다시 맞춥니다. 히어로만의 별도 브레이크포인트는 없습니다.
기본 사용법 (Basic Usage)#
Hero(
child: Text('Welcome').headlineSmall.onSurface,
)
Hero(
layout: CoreHeroLayout.split,
media: Text('Media').titleMedium.onSurface,
child: Text('Ship the product').headlineSmall.onSurface,
)
Props / Parameters#
W는 합성 콘텐츠 타입으로 Flutter에서는 Widget, Web에서는 Component입니다.
| 속성 | 타입 | 설명 |
|---|---|---|
layout |
CoreHeroLayout? |
center
(기본) 또는
split
.
null
이면 스타일 체인의
layout
, 그다음 [CoreHeroStyle.defaultLayout].
|
media |
W? |
split 의 뒤쪽 칸. center 에서는 무시됩니다. |
child |
W |
필수 카피. center 에서는 가운데, split 에서는 앞(또는 위). |
imageUrl |
String? |
배경 이미지. cover. 전경 media 가 아닙니다. |
semanticLabel |
String? |
히어로 영역의 접근 가능한 이름. 현재 영역 역할의 플랫폼별 조건은 접근성 절 참고. |
appear |
bool |
전경이 뷰포트에 처음 들어올 때 한 번 올라오며 나타납니다. 기본 false. |
heroStyle |
CoreHeroStyle? |
배경색, 오버레이, 최소 높이, 패딩, 분할 간격, 등장 모션. |
heroSectionStyle 은 heroStyle 의 이전 이름입니다. HeroSection 생성자는
그 이름으로 받아 Hero 로 넘기고, deprecated 입니다.
스타일 시스템 (Style System)#
| 필드 | 기본 | 설명 |
|---|---|---|
backgroundColor | surfaceContainer | 배경 채움. |
overlayColor |
없음 | 있으면 배경 이미지 위에 스크림. 기본값을 두지 않아, 없는 히어로에는 레이어 자체가 없습니다. |
minHeight | 400 | 최소 높이 (논리 px). |
padding | space32 | 바깥 여백. |
layout |
center |
위젯 layout 이 null 일 때의 기본 배치. |
splitGapStyle |
Gap space32 |
카피와 미디어 사이. Gap 에 그대로 넘깁니다. |
appearDuration |
moderate |
appear: true 일 때만. |
appearOffset | space16 | 전경이 올라오는 거리. |
appearCurve | enter | 등장 이징. |
동작 스펙 (Behavior)#
-
layout을 생략하면 스타일 체인의 레이아웃을 읽고 기본center로 배치합니다.center는media를 렌더하지 않으며split도media가 없으면 카피만 가운데에 둡니다. - 배경 이미지와 오버레이는 전경 콘텐츠 뒤에 남습니다.
minHeight는 논리 px 단위의 최소 높이이며 콘텐츠가 크면 영역도 늘어납니다. -
appear: true는 전경이 뷰포트에 들어왔을 때 한 번만 등장시킵니다.appearDuration은Duration,appearOffset은 논리 px 단위이고 모션 줄이기 설정에서는 전환 시간이 0이 됩니다. - Hero 자체에는 클릭·선택·닫기 콜백이 없습니다. 전경의 링크나 버튼 동작은 자식이 담당합니다.
사용 가이드라인 (Usage Guidelines)#
- 페이지의 첫 구간으로 씁니다. 카드 안이나 다이얼로그 안에서는 쓰지 않습니다.
- 카피와 제품 이미지를 나란히 둘 때는
split입니다. 카피 안에Row를 직접 짜지 않습니다. - 배경 사진은
imageUrl입니다. 읽혀야 하는 그림은media입니다. semanticLabel은 영역을 구분할 수 있는 이름으로 줍니다. 현재 Web의 무라벨 region 제약은 아래 접근성 절을 참고하세요.
카피·미디어가 함께 놓인 페이지 인트로에는 Hero를, 본문 안의 개별 콘텐츠 묶음에는 Card를 사용합니다.
접근성 (Accessibility)#
역할#
- Flutter:
semanticLabel이 있을 때만 region 랜드마크를 만듭니다. -
Web: 현재 루트는 라벨 유무와 관계없이
role="region"을 내보내며,semanticLabel이 있으면aria-label을 추가합니다.
키보드#
Hero 자신이 처리하는 키는 없습니다. child / media 의 버튼·링크가 각자의
키보드 동작을 가져옵니다.
스크린 리더#
랜드마크 이름은 semanticLabel 에서 옵니다. 읽는 콘텐츠는 child / media 가
제공하고, split 에서는 카피 다음 미디어 순서입니다.
포커스 관리#
Hero 는 페이지 흐름 안의 블록이므로 진입·이탈 시 포커스를 옮기거나 가두지 않습니다. 자식의 포커스 동작을 유지합니다.
알려진 제약#
-
Web의 무라벨 region:
semanticLabel을 생략하면 이름 없는 랜드마크가 남습니다. 의미 있는 영역 이름을 명시해 현재 제약을 우회하세요. imageUrl은 장식입니다. 대체 텍스트를 갖지 않습니다. 정보가 있는 그림은media에 두고 그 안에서 이름을 줍니다.appear는 전경만 움직입니다. 배경과 오버레이는 그대로입니다. 한 번 나타난 뒤 되감기지 않습니다. 모션 줄이기가 켜지면 이동 시간이 0 입니다.
크로스 플랫폼 차이점 (Platform Differences)#
Flutter 와 Web 은 같은 이름입니다. layout, media, child, imageUrl,
semanticLabel, appear, heroStyle.
접히는 기준도 같습니다. 태블릿 / 768px 미만은 세로, 그 이상은 가로이고 미디어가 뒤입니다.
| 항목 | Flutter | Web |
|---|---|---|
| 폭 변경 감지 | MediaQuery와 resolver 재실행 |
브라우저 viewport resize 리스너와 resolver 재실행 |
| 등장 감지 | 렌더 위치를 읽는 전경 위젯 | DOM 관찰과 전경 CSS 전환; 서버 렌더링만으로 등장 감지는 실행되지 않음 |
| 영역 의미 | 라벨을 전달했을 때 CoUISemantics(role: .region) |
현재 <div role="region">를 항상 출력하고 라벨이 있으면 aria-label 추가 |
| 배경 이미지 | NetworkImage + DecorationImage(fit: .cover) |
CSS 배경 이미지와 cover |
남은 갭#
이 PR 범위 밖입니다.
fillViewport— 첫 화면을 꽉 채우는 높이- 프루프 스트립, 비디오 슬롯, 콜라주
Figma 의 Hero 는 Layout=center | split 컴포넌트 세트입니다. center 는
가운데 카피만, split 은 카피 다음 미디어입니다. 좁은 화면의 세로 쌓임은
런타임 규칙이고 캔버스는 넓은 자세를 그립니다.