Banner#
페이지 상단에 중요한 정보, 경고, 오류 등을 표시하는 배너 컴포넌트입니다. CoBanner는 Flutter와 Web에서 동일한 named properties API를 제공하며,
default/info/success/warning/destructive
5개 variant를 지원합니다.
Live Preview#
class BannerInfoExample extends StatefulComponent {
const BannerInfoExample({super.key});
@override
State<BannerInfoExample> createState() => _BannerInfoExampleState();
}
class _BannerInfoExampleState extends State<BannerInfoExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return CoBanner.info(
message: const Text('새로운 업데이트가 준비되었습니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerInfoExample extends StatefulWidget {
const BannerInfoExample({super.key});
@override
State<BannerInfoExample> createState() => _BannerInfoExampleState();
}
class _BannerInfoExampleState extends State<BannerInfoExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return CoBanner.info(
message: const Text('새로운 업데이트가 준비되었습니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerWarningExample extends StatefulComponent {
const BannerWarningExample({super.key});
@override
State<BannerWarningExample> createState() => _BannerWarningExampleState();
}
class _BannerWarningExampleState extends State<BannerWarningExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return CoBanner.warning(
message: const Text('세션이 5분 후에 만료됩니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerWarningExample extends StatefulWidget {
const BannerWarningExample({super.key});
@override
State<BannerWarningExample> createState() => _BannerWarningExampleState();
}
class _BannerWarningExampleState extends State<BannerWarningExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return CoBanner.warning(
message: const Text('세션이 5분 후에 만료됩니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerDestructiveExample extends StatefulComponent {
const BannerDestructiveExample({super.key});
@override
State<BannerDestructiveExample> createState() =>
_BannerDestructiveExampleState();
}
class _BannerDestructiveExampleState extends State<BannerDestructiveExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return CoBanner.destructive(
message: const Text('요청을 처리하는 중 문제가 발생했습니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerDestructiveExample extends StatefulWidget {
const BannerDestructiveExample({super.key});
@override
State<BannerDestructiveExample> createState() =>
_BannerDestructiveExampleState();
}
class _BannerDestructiveExampleState extends State<BannerDestructiveExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return CoBanner.destructive(
message: const Text('요청을 처리하는 중 문제가 발생했습니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerSuccessExample extends StatefulComponent {
const BannerSuccessExample({super.key});
@override
State<BannerSuccessExample> createState() => _BannerSuccessExampleState();
}
class _BannerSuccessExampleState extends State<BannerSuccessExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return CoBanner.success(
message: const Text('변경 사항이 성공적으로 저장되었습니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
class BannerSuccessExample extends StatefulWidget {
const BannerSuccessExample({super.key});
@override
State<BannerSuccessExample> createState() => _BannerSuccessExampleState();
}
class _BannerSuccessExampleState extends State<BannerSuccessExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return CoBanner.success(
message: const Text('변경 사항이 성공적으로 저장되었습니다.'),
onDismiss: () => setState(() => _show = false),
);
}
return CoButton(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show banner'),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 페이지 전체에 영향을 미치는 시스템 상태 메시지를 표시할 때 (점검 공지, 서비스 알림)
- 사용자가 취해야 할 중요한 액션을 안내할 때 (세션 만료 경고, 구독 만료 알림)
- 사용자가 방금 완료한 작업의 결과를 페이지 상단에 지속적으로 표시할 때
대신 다른 컴포넌트를 사용하세요:
Toast: 일시적으로 나타났다가 자동으로 사라지는 알림이 필요할 때Alert: 특정 콘텐츠 영역 내에 인라인으로 표시하는 경고 메시지가 필요할 때Dialog: 사용자의 즉각적인 응답이 필요한 중요한 알림에는 모달 다이얼로그 사용
기본 사용법 (Basic Usage)#
CoUI는 Flutter와 Web에서 동일한 API를 제공합니다. 아래 예제 코드는 양쪽 플랫폼에서 그대로 사용할 수 있습니다.
// 정보 배너
CoBanner.info(
message: Text('시스템 점검이 예정되어 있습니다.'),
)
// 성공 배너 (해제 버튼 포함)
CoBanner.success(
message: Text('저장이 완료되었습니다.'),
onDismiss: handleDismiss,
)
// 경고 배너 (액션 포함)
CoBanner.warning(
message: Text('세션이 곧 만료됩니다.'),
description: Text('지금 연장하면 작업을 이어서 할 수 있습니다.'),
action: CoButton(
variant: CoreButtonVariant.outline,
onPressed: handleExtendSession,
child: Text('연장하기'),
),
onDismiss: handleDismiss,
)
// 오류 배너
CoBanner.destructive(
message: Text('요청을 처리하는 중 오류가 발생했습니다.'),
onDismiss: handleDismiss,
)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
message |
Widget / Component |
필수 | 배너에 표시할 메시지 |
description |
Widget? / Component? |
null |
메시지 아래에 표시되는 선택적 보조 설명 |
variant |
CoreBannerVariant |
info |
배너 변형 (
default_
,
info
,
success
,
warning
,
destructive
)
|
onDismiss |
VoidCallback? / CoreVoidCallback? |
null |
닫기 버튼 클릭 핸들러. null이면 닫기 버튼 미표시 |
action |
Widget? / Component? |
null |
배너 우측에 표시할 액션 위젯 |
showIcon |
bool |
true |
variant 별 기본 아이콘 표시 여부 |
bannerStyle |
CoreBannerStyle<Color>? / CoreBannerStyle<CoreColor>? |
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
Banner 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreBannerStyle<Clr> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 시맨틱 enum (variant) 과 behaviour (message
/ description / action / onDismiss / showIcon) 는 위젯 파라미터로 직접 전달합니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (
variant,message,description,action,onDismiss,showIcon) -
chrome / dimensional / 슬롯 스타일:
CoreBannerStyle한 곳으로 (backgroundColor/foregroundColor/borderColor/borderWidth/borderRadius/paddingH/paddingV/gap/titleStyle/descriptionStyle/iconStyle/actionButtonStyle/closeButtonStyle)
Resolve chain#
design system default for banner
→ CoreBannerTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.bannerStyle // 인스턴스별
각 nested 슬롯 스타일 (titleStyle / descriptionStyle / iconStyle / actionButtonStyle
/ closeButtonStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.
슬롯 매핑 (CoreBannerStyle 13 필드)#
| 필드 | 타입 (Flutter) | 적용 영역 |
|---|---|---|
backgroundColor |
Color? |
배너 배경 색 (variant 기본값 위에 오버라이드) |
foregroundColor | Color? | 메시지 / 설명 / 아이콘 전경 색 |
borderColor | Color? | 좌측 액센트 보더 색 |
borderWidth | double? | 보더 두께 (px) |
borderRadius | double? | 모서리 반지름 (px) |
paddingH | double? | 좌/우 패딩 (px) |
paddingV | double? | 위/아래 패딩 (px) |
gap | double? | 아이콘 / 콘텐츠 / 액션 간격 (px) |
titleStyle |
CoreTextStyle<Color>? |
메시지 텍스트 스타일 |
descriptionStyle |
CoreTextStyle<Color>? |
설명 텍스트 스타일 |
iconStyle |
CoreIconStyle<Color>? |
variant 아이콘 스타일 |
actionButtonStyle |
CoreButtonStyle<Color>? |
액션 버튼 슬롯에 패스스루되는 chrome (호스트가 CoButton 을 주입한 경우 전달) |
closeButtonStyle |
CoreButtonStyle<Color>? |
닫기 버튼 chrome (현재 구현은
foregroundColor
/
labelStyle.color
만 적용; 완전
CoButton
wrap 은 #1322 이월)
|
Migration — 옛 평면 chrome → 새 위치 매핑#
기존 CoreBannerTheme 의 평면 chrome (backgroundColor / borderColor /
borderRadius) 은 호환을 위해 유지되지만, 새 코드는 bannerStyle 슬롯을 사용하세요:
| 기존 (legacy theme 필드) | 새 위치 |
|---|---|
CoreBannerTheme.backgroundColor |
bannerStyle.backgroundColor 또는 CoreBannerTheme.style.backgroundColor |
CoreBannerTheme.borderColor |
bannerStyle.borderColor 또는 CoreBannerTheme.style.borderColor |
CoreBannerTheme.borderRadius |
bannerStyle.borderRadius 또는 CoreBannerTheme.style.borderRadius |
| 인스턴스 padding 오버라이드 (불가능) | bannerStyle.paddingH / paddingV |
| 인스턴스 메시지 텍스트 스타일 (불가능) | bannerStyle.titleStyle |
| 인스턴스 설명 텍스트 스타일 (불가능) | bannerStyle.descriptionStyle |
| 인스턴스 아이콘 사이즈/색 (불가능) | bannerStyle.iconStyle |
사용 예 (Flutter)#
CoBanner.warning(
message: Text('세션이 곧 만료됩니다.'),
description: Text('지금 연장하면 작업을 이어서 할 수 있습니다.'),
bannerStyle: CoreBannerStyle(
paddingH: 24,
paddingV: 16,
borderRadius: 12,
titleStyle: CoreTextStyle(fontSize: 14, fontWeight: 600),
descriptionStyle: CoreTextStyle(fontSize: 12),
iconStyle: CoreIconStyle(size: 20),
),
onDismiss: handleDismiss,
)
사용 예 (Web)#
CoBanner.warning(
message: text('세션이 곧 만료됩니다.'),
description: text('지금 연장하면 작업을 이어서 할 수 있습니다.'),
bannerStyle: CoreBannerStyle<CoreColor>(
paddingH: 24,
paddingV: 16,
borderRadius: 12,
),
onDismiss: handleDismiss,
)
변형 (Variants)#
Info#
일반적인 정보 전달에 사용합니다.
CoBanner.info(
message: Text('새로운 업데이트가 있습니다.'),
)
Success#
성공적인 작업 완료를 알릴 때 사용합니다.
CoBanner.success(
message: Text('변경 사항이 저장되었습니다.'),
)
Warning#
사용자의 주의가 필요한 상황에 사용합니다.
CoBanner.warning(
message: Text('구독이 7일 후 만료됩니다.'),
)
Destructive (Error)#
오류 또는 실패 상태를 알릴 때 사용합니다.
CoBanner.destructive(
message: Text('네트워크 연결에 실패했습니다.'),
)
동작 스펙 (Behavior)#
인터랙션#
- 닫기 버튼 클릭:
onDismiss콜백 실행. 배너 숨김 처리는 부모 위젯에서 상태로 관리 - 액션 버튼 클릭:
action위젯에 정의된 콜백 실행 - 호버: 닫기 버튼 opacity 상승
상태 전환#
- 배너 표시: 슬라이드 다운 또는 페이드인 (부모에서
AnimatedSwitcher사용 권장) - 배너 닫기:
onDismiss호출 후 부모에서 상태를 변경하여 제거
테마 오버라이드#
프로젝트 레벨에서 CoreComponentTheme.banner로 기본값을 재정의할 수 있습니다.
CoreComponentTheme(
banner: CoreBannerTheme(
backgroundColor: CoreColor(0xFFF8F9FA),
borderColor: CoreColor(0xFF6366F1),
borderRadius: 12.0,
showIcon: true,
),
)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
중요도에 맞는 variant 사용
// 시스템 점검 공지: info
CoBanner.info(
message: Text('2026-03-15 02:00~04:00 시스템 점검이 예정되어 있습니다.'),
)
// 보안 경고: warning
CoBanner.warning(
message: Text('비밀번호를 90일 이상 변경하지 않았습니다.'),
action: TextButton(
onPressed: handleChangePassword,
child: Text('변경하기'),
),
)
올바른 variant를 사용하면 사용자가 메시지의 중요도를 즉시 파악할 수 있습니다.
❌ Don't#
여러 배너를 동시에 표시 금지
// ❌ 여러 배너 동시 표시
Column(
children: [
CoBanner.info(message: Text('점검 예정')),
CoBanner.success(message: Text('저장 완료')),
CoBanner.warning(message: Text('세션 만료')),
],
)
여러 배너가 동시에 표시되면 사용자에게 압도적인 느낌을 주고 콘텐츠 영역을 지나치게 차지합니다. 가장 중요한 하나만 표시하세요.
✅ Do#
해제 가능한 배너에는 항상 onDismiss 제공
CoBanner.info(
message: Text('새로운 기능이 추가되었습니다. 지금 확인해보세요!'),
onDismiss: handleDismissBanner,
action: TextButton(
onPressed: handleLearnMore,
child: Text('자세히 보기'),
),
)
사용자가 불필요한 배너를 닫을 수 있어야 콘텐츠에 집중할 수 있습니다.
❌ Don't#
destructive variant를 일반 정보 표시에 남용 금지
// ❌ 단순 안내에 destructive variant 사용
CoBanner.destructive(
message: Text('이 기능은 프리미엄 회원 전용입니다.'),
)
destructive variant는 실제 오류 상황에만 사용해야 합니다. 남용하면 사용자가 진짜 오류를 놓칠 수 있습니다.
✅ Do#
액션이 필요한 배너에는 action 버튼을 추가하세요.
CoBanner.warning(
message: Text('구독이 3일 후 만료됩니다.'),
action: TextButton(
onPressed: handleRenewSubscription,
child: Text('갱신하기'),
),
onDismiss: handleDismiss,
)
사용자가 즉각적인 조치를 취할 수 있도록 명확한 액션 버튼을 제공하면 전환율이 높아집니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | 배너 내 인터랙티브 요소(액션 버튼, 닫기 버튼)로 이동 |
Enter / Space | 포커스된 버튼 활성화 |
Escape | 닫기 버튼이 있는 경우 배너 닫기 |
스크린 리더#
- Flutter:
Semantics(container: true, label: 'Banner')적용 - Web:
role="banner"속성 자동 적용
크로스 플랫폼 차이점 (Platform Differences)#
CoBanner는 Flutter와 Web에서 동일한 named properties API를 사용합니다. 플랫폼별로 타입이 다른 부분만 차이가 있습니다.
| 항목 | Flutter | Web |
|---|---|---|
message / description / action 타입 |
Widget |
Component |
onDismiss 콜백 타입 |
VoidCallback? |
CoreVoidCallback? |
| 배치 방식 | Column 최상단 또는 Scaffold 내 삽입 |
페이지 상단 고정 (position: sticky) |
| 애니메이션 | AnimatedSwitcher 등 Flutter 애니메이션 | CSS transition |