Alert
정보, 성공, 경고, 오류 메시지를 표시하는 알림 영역 컴포넌트
목차
다양한 상태(정보, 성공, 경고, 오류)를 시각적으로 구분하여 메시지를 표시하는 알림 영역 컴포넌트입니다. Flutter와 Web이 동일한 named properties API(title,
description, variant, layout, leading, onDismiss,
action)를 사용합니다. layout 이 stacked(기본) 면 제목과 설명이 쌓인 패널이고, inline
이면 한 줄짜리 상태 띠입니다.
기본 사용법 (Basic Usage)#
// 기본 알림
Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
)
// 정보 알림 (variant named constructor)
Alert.info(
title: '안내',
description: '새로운 업데이트가 있습니다.',
)
// 성공 알림 (닫기 버튼 포함)
Alert.success(
title: '저장 완료',
description: '변경 사항이 성공적으로 저장되었습니다.',
onDismiss: handleDismiss,
)
// 오류 알림
Alert.destructive(
title: '오류 발생',
description: '요청을 처리하는 중 문제가 발생했습니다.',
)
// 한 줄 상태 띠 (inline) — 앞자리에 Badge, 뒤에 caller 의 아이콘
Alert(
layout: CoreAlertLayout.inline,
leading: Badge(
tone: CoreBadgeTone.success,
showDot: true,
child: Text('Synced'),
),
title: 'All changes saved to the cloud.',
action: Icon(
LucideIcons.chevronDown,
iconStyle: CoreIconStyle(
size: CoreIconSize.size16,
color: CoreColor.token(CoreColors.onSurfaceSubtle),
),
),
)
// 기본 알림
Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
)
// 정보 알림
Alert.info(
title: '안내',
description: '새로운 업데이트가 있습니다.',
)
// 성공 알림 (닫기 버튼 포함)
Alert.success(
title: '저장 완료',
description: '변경 사항이 성공적으로 저장되었습니다.',
onDismiss: handleDismiss,
)
// 오류 알림
Alert.destructive(
title: '오류 발생',
description: '요청을 처리하는 중 문제가 발생했습니다.',
)
// 한 줄 상태 띠 (inline) — 앞자리에 Badge, 뒤에 caller 의 아이콘
Alert(
layout: CoreAlertLayout.inline,
leading: Badge(
tone: CoreBadgeTone.success,
showDot: true,
child: Text('Synced'),
),
title: 'All changes saved to the cloud.',
action: Icon(
LucideIcons.chevronDown,
iconStyle: CoreIconStyle(
size: CoreIconSize.size16,
color: CoreColor.token(CoreColors.onSurfaceSubtle),
),
),
)
Live Preview#
코드 보기
class AlertDefaultExample extends StatefulComponent {
const AlertDefaultExample({super.key});
@override
State<AlertDefaultExample> createState() => _AlertDefaultExampleState();
}
class _AlertDefaultExampleState extends State<AlertDefaultExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertDefaultExample extends StatefulWidget {
const AlertDefaultExample({super.key});
@override
State<AlertDefaultExample> createState() => _AlertDefaultExampleState();
}
class _AlertDefaultExampleState extends State<AlertDefaultExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 페이지 내 고정된 위치에서 중요한 상태 메시지를 안내할 때
- 폼 제출 결과(성공/오류)를 인라인으로 표시할 때
- 사용자가 반드시 인지해야 하는 경고나 안내를 표시할 때
대신 다른 컴포넌트를 사용하세요:
- 입력값 자체가 거부된 경우에는 FormField 의 오류·검증 표면을 씁니다. 검증 힌트는 ValidatorHint, 검증 표시 래퍼는 Validated 를 확인하세요. 요청 실패와 필드 값 문제는 다른 자리입니다
-
EmptyState: 실패한 영역에 보여줄 내용이 없어 그 자리를 대신 채워야 할 때. 내용이 남아 있고 한 동작만 실패했으면
Alert를 그 옆에 둡니다 - Toast: 본문에 남는 패널 없이 일시적인 오버레이 피드백이 필요할 때
- Banner: 특정 영역 옆이 아니라 페이지 상단의 공지 스트립이 필요할 때
- Dialog: 메시지 표시 외에 모달에서 사용자의 확인 응답을 받아야 할 때
Props / Parameters#
W 는 Flutter의 Widget, Web의 Jaspr Component 입니다. W?
는 선택 슬롯입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title |
String |
필수 | 알림 제목 — 이 컴포넌트가 반드시 갖는 한 줄 |
description |
String? |
null |
제목 아래 보조 설명 (선택). layout: inline 에서는 그리지 않습니다 |
variant |
CoreAlertVariant |
neutral |
알림 종류 (
neutral
/
info
/
success
/
warning
/
destructive
). 면 색과 아이콘 색을 정합니다
|
layout |
CoreAlertLayout |
stacked |
내용을 놓는 방식. stacked 는 제목 + 설명이 쌓인 패널, inline 은 한 줄 상태 띠 |
leading |
W? |
null |
톤 아이콘 대신 앞자리에 놓을 위젯. 두 layout 모두에서 쓸 수 있고, null 이면 variant 의 톤 아이콘을 그립니다 |
showIcon |
bool |
CoreAlertContract.defaultShowIcon (true) |
앞자리(톤 아이콘 또는
leading
)를 그릴지.
false
면 앞자리와 그 뒤 간격이 함께 빠지고 글자가 패널 안쪽 여백에서 시작합니다
|
onDismiss |
VoidCallback? (Flutter) / CoreVoidCallback? (Web) |
null |
닫기 버튼 클릭 핸들러. null이면 닫기 버튼 미표시 |
action |
W? |
null |
알림 우측에 표시할 액션 위젯 |
alertStyle |
CoreAlertStyle? |
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
Alert 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreAlertStyle 단일 슬롯으로 흐릅니다. 시맨틱 enum (variant) 과 behaviour (title
/ description / action / onDismiss) 는 위젯 파라미터로 직접 전달합니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (
variant,layout,title,description,leading,action,onDismiss) -
chrome / dimensional / 슬롯 스타일:
CoreAlertStyle한 곳으로 (backgroundColor/minHeight/borderRadius/padding/iconContentGapStyle/titleBottomMargin/titleStyle/descriptionStyle/iconStyle/actionButtonStyle/closeButtonStyle)
Resolve chain#
design system default for alert // defaultsByLayout[layout] + defaultsByVariant[variant]
→ CoreAlertTheme.style // 프로젝트 공통
→ CoreAlertTheme.variantStyles[variant]
→ parent component slot override
→ widget.alertStyle // 인스턴스별
layout 은 Theme 에 따로 슬롯이 없습니다. CoreAlertTheme.style 과 alertStyle
로 덮은 패딩 · 간격 · 아이콘 크기 · 제목 스타일 · minHeight 는 두 layout 에 똑같이 적용됩니다 — 한 layout 만 바꾸려면 그 layout 의 Alert 에만
alertStyle 을 넘깁니다.
각 nested 슬롯 스타일 (titleStyle / descriptionStyle / iconStyle / actionButtonStyle
/ closeButtonStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다.
CoreAlertStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Alert panel background fill colour. |
foregroundColor |
CoreColor? |
Foreground colour applied to title / description / icon when the per-slot style does not override it. |
borderColor |
CoreColor? |
Border stroke colour. Alert draws no border of its own since 0.131 — it fills opaquely, and [defaultBorderWidth] is zero. The slot stays so a caller can opt one back in by stating a colour AND a width; a colour alone strokes nothing. |
titleColor |
CoreColor? |
Title colour.
null
→ the variant's
titleColor
in [defaultsByVariant] (a tone's
on-{tone}-container
), then the layout's row in [defaultsByLayout] ([defaultTitleColor] when stacked) for the variant that states none. Wins over [foregroundColor] for the title on both platforms.
|
dismissColor |
CoreColor? |
Dismiss-glyph colour.
null
→ [defaultDismissColor]. Replaces the opacity multiplier this used to carry — a colour times an alpha resolves against whatever panel sits behind it, which no guard can check. A token can be.
|
borderWidth |
double? |
Border stroke width (logical px). null → [defaultBorderWidth], which is now zero. |
borderRadius | CoreBorderRadius? | Border radius. |
padding |
CoreEdgeInsets? |
Padding. null → the layout's row in [defaultsByLayout] ([defaultPadding] when stacked). |
minHeight |
double? |
Minimum box height (logical px).
null
→ the layout's row in [defaultsByLayout] ([defaultMinHeight] when stacked, none inline).
|
iconContentGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the gap between the leading icon and the content column / trailing slots — forwarded straight to the
Gap
widget that separates the slots. Merged on top of the layout's row in [defaultsByLayout] ([defaultIconContentGapStyle] when stacked).
|
titleBottomMargin |
double? |
Bottom margin between the title and description (logical px).
null
defers to [defaultTitleBottomMargin].
|
titleStyle |
CoreTextStyle? |
Title text style override, merged on top of the layout's row in [defaultsByLayout] ([defaultTitleStyle] when stacked). |
descriptionStyle |
CoreTextStyle? |
Description text style override. |
iconStyle |
CoreIconStyle? |
Leading icon style override, merged on top of the layout's row in [defaultsByLayout] ([defaultIconStyle] when stacked). Its size is the ambient icon size a caller's
leading
widget receives.
|
actionButtonStyle |
CoreButtonStyle? |
Action button chrome.
Deliberately has no default* — absence is the design.
The action is a caller-supplied widget, and the alert only reaches into it when the caller asked for chrome: the Web widget rebuilds a
Button
action with
resolved.actionButtonStyle!.merge(…)
only in the
when resolved.actionButtonStyle != null
arm of its pattern match, and renders the action untouched otherwise. A constant would make that rebuild unconditional, so every alert would re-style a button it was handed — the composed
Button
's own variant defaults are the baseline here, and overriding them from the parent is exactly what this slot is for when a caller opts in. Unlike [closeButtonStyle], which the alert draws itself and therefore does default ([defaultCloseButtonStyle]), this affordance is not the alert's to design. Asymmetry worth knowing: only Web consumes it. The Flutter resolver forwards it onto
ResolvedAlert.actionButtonStyle
, but the Flutter widget renders
action!
as-is and never reads that slot, so seat (4) of the 1:1:1:1:1:1 mapping is empty on that platform — a wiring gap, not a default gap.
|
closeButtonStyle |
CoreButtonStyle? |
Close (dismiss) button style override. The single entry point for the dismiss-button chrome — padding / corner radius / close-glyph (
leadingIconStyle
) / hover transition (
animationDuration
) / focus ring (
focusOutlineStyle
) / idle-vs-hover foreground (
foregroundColor
/
hoverForegroundColor
). Merged onto [defaultCloseButtonStyle] and raw-forwarded to
Button(variant:.plain, buttonStyle: …)
(the
plain
variant owns the transparent-fill / hover-fade semantics).
|
CoreAlertStyle 변형별 기본값 (CoreAlertVariantStyle)#
| 필드 | neutral |
info |
success |
warning |
destructive |
|---|---|---|---|---|---|
backgroundColor |
surfaceContainer | infoContainer | successContainer | warningContainer | errorContainer |
descriptionColor |
onSurfaceVariant | onSurfaceVariant | onSurfaceVariant | onSurfaceVariant | onSurfaceVariant |
iconColor |
onSurfaceVariant | onInfoContainer | onSuccessContainer | onWarningContainer | onErrorContainer |
titleColor |
— | onInfoContainer | onSuccessContainer | onWarningContainer | onErrorContainer |
제목은 톤의 색입니다 — info · success · warning · destructive
는 아이콘과 같은
on{Tone}Container 로 그려, 아이콘을 끈 알림도 제목만으로 톤이 읽힙니다. neutral
은
제목 색을 갖지 않고 layout 의 색을 씁니다(stacked 는 onSurface,
inline 은
onSurfaceVariant). 본문은 모든 톤에서 onSurfaceVariant
입니다. 톤 제목은 불투명한
톤 면 위에서 약 8:1 로, 본문 기준(4.5:1)을 넉넉히 넘습니다. titleColor 와
foregroundColor 를 둘 다 넘기면 제목은 titleColor 를 따릅니다(양 플랫폼 동일).
채움이 불투명한 것이 중요합니다. 이전 모델은 톤을 10 % 로 깔고 그 위에 같은 톤을
올려서, 라이트 모드 warning 이 1.71 까지 떨어졌습니다. 지금은 라벨이 페이지가 아니라
자기 패널을 배경으로 삼습니다.
레이아웃 (Layout)#
layout 은 내용을 얼마나 보여 주고 얼마나 자리를 차지할지를 정합니다. 면 · 반경 · 아이콘 색은 두 layout 모두 variant 표 그대로입니다.
| 칸 | stacked (기본) | inline |
|---|---|---|
| 모양 | 제목 아래 설명이 쌓인 패널 | 한 줄 상태 띠 |
| 패딩 | 위아래 12 · 앞 16 · 끝 12 | 위아래 10 · 좌우 14 |
| 최소 높이 | 64 | 없음 — 내용 높이 (Badge md 를 앞에 두면 10 + 24 + 10 = 44) |
| 앞자리 ↔ 내용 간격 | 12 | 10 |
| 톤 아이콘 | 18 | 16 |
| 제목 | labelLarge · 600 · 톤 색 (neutral 은 onSurface) |
bodySmall · 톤 색 (neutral 은 onSurfaceVariant), 한 줄 · 넘치면 말줄임 |
| 설명 | onSurfaceVariant | 그리지 않음 |
stacked 는 최소 높이(minHeight, 64) 안에서 내용이 세로 중앙에 옵니다. 안쪽
행도 가운데 정렬이라 아이콘 · 본문 · 행동 · 닫기 표시가 같은 가운데 선을
공유합니다. 두 줄짜리 Alert 은 하나의 덩어리로 읽히고, 제목만 있는 Alert 은 최소
높이가 열어둔 박스의 가운데에 앉습니다. 패딩은 앞과 끝이 다른 값이라 읽는
방향(directional)으로 적혀 있어 RTL 로케일에서도 같은 쪽에 붙습니다.
inline 은 한 줄에 목소리가 하나라서 제목이 본문 크기(bodySmall)입니다 — 톤은 글자
색과 앞의 배지나 아이콘이 함께 나릅니다. description 을 넘겨도 그리지 않으며, 접근성 이름은 두
layout 모두 title 이라 말줄임으로 잘려도 전체 제목이 읽힙니다.
앞자리 슬롯 (leading)#
leading 은 톤 아이콘을 대신합니다 — 둘을 함께 그리지 않고, null 이면 variant 의
톤 아이콘을 그립니다. 두 layout 모두에서 쓸 수 있습니다. 슬롯에는 톤 아이콘의 색과
그 layout 의 아이콘 크기가 아이콘 테마로 내려가므로, 아이콘 하나를 그대로 넘겨도 원래
아이콘과 같은 모양이 됩니다. Badge 처럼 자기 색을 가진 위젯은 그 색을 그대로 씁니다.
showIcon: false 는 이 자리를 통째로 끕니다 — 톤 아이콘이든 넘긴 leading 이든 그리지
않고, 그 뒤 간격도 함께 빠집니다.
색과 크기가 닿는 곳은 아이콘 테마까지입니다. 아이콘이 아닌 내용(맨 Text 등)은 슬롯이
칠하지도 줄 높이를 바꾸지도 않아, 양 플랫폼 모두 옆의 본문과 같은 색과 줄 높이를
상속합니다.
// stacked 에서도 같은 슬롯 — 톤 아이콘 자리에 Badge 를 둔다.
Alert.warning(
leading: Badge(tone: CoreBadgeTone.warning, child: Text('Beta')),
title: '미리 보기 기능',
description: '출시 전까지 동작이 바뀔 수 있습니다.',
)
사용 예 (Flutter)#
Alert.success(
title: '저장 완료',
description: '변경 사항이 성공적으로 저장되었습니다.',
alertStyle: CoreAlertStyle(
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space16,
),
borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
titleStyle: CoreTextStyle.token(CoreTextStyles.labelLarge),
descriptionStyle: CoreTextStyle.token(CoreTextStyles.bodySmall),
iconStyle: CoreIconStyle(size: CoreIconSize.size20),
),
onDismiss: handleDismiss,
)
사용 예 (Web)#
Alert.success(
title: '저장 완료',
description: '변경 사항이 성공적으로 저장되었습니다.',
alertStyle: CoreAlertStyle(
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space16,
),
borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
),
onDismiss: handleDismiss,
)
빠른 오버라이드 (Chain)#
이미 만든 Alert 인스턴스에 alertStyle을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다.
.radius4처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius4 ==
CoreRadius.radius4) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class AlertChainExample extends StatefulWidget {
const AlertChainExample({super.key});
@override
State<AlertChainExample> createState() => _AlertChainExampleState();
}
class _AlertChainExampleState extends State<AlertChainExample> {
bool _showCombo = true;
bool _showFull = true;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
if (_showCombo)
Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
onDismiss: () => setState(() => _showCombo = false),
).radius16.primary
else
Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _showCombo = true),
child: const Text('Show alert'),
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
if (_showFull)
Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
onDismiss: () => setState(() => _showFull = false),
).withStyle(
const CoreAlertStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
foregroundColor: CoreColor.token(
CoreColors.onTertiaryContainer,
),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
)
else
Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _showFull = true),
child: const Text('Show alert'),
),
],
);
}
}
class AlertChainExample extends StatefulComponent {
const AlertChainExample({super.key});
@override
State<AlertChainExample> createState() => _AlertChainExampleState();
}
class _AlertChainExampleState extends State<AlertChainExample> {
bool _showCombo = true;
bool _showFull = true;
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
if (_showCombo)
Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
onDismiss: () => setState(() => _showCombo = false),
).radius16.primary
else
Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _showCombo = true),
child: const Text('Show alert'),
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
if (_showFull)
Alert(
title: 'Heads up!',
description: 'You can add components to your app using the cli.',
onDismiss: () => setState(() => _showFull = false),
).withStyle(
const CoreAlertStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
foregroundColor: CoreColor.token(
CoreColors.onTertiaryContainer,
),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
)
else
Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _showFull = true),
child: const Text('Show alert'),
),
],
classes: 'flex flex-col items-start',
);
}
}
Named constructors#
-
Alert.info(...),Alert.success(...),Alert.warning(...),Alert.destructive(...)— 각 variant에 대한 편의 생성자.
변형 (Variants)#
Default#
Alert(
title: 'Heads up!',
description: '표준 surface 색상을 사용하는 기본 알림입니다.',
)
Info#
Alert.info(
title: '정보',
description: '시스템 점검이 예정되어 있습니다.',
)
Success#
Alert.success(
title: '완료',
description: '파일이 성공적으로 업로드되었습니다.',
onDismiss: handleDismiss,
)
Warning#
Alert.warning(
title: '주의',
description: '이 작업은 되돌릴 수 없습니다.',
)
Destructive (Error)#
Alert.destructive(
title: '오류',
description: '네트워크 연결을 확인해 주세요.',
)
Inline (한 줄 상태 띠)#
Alert(
layout: CoreAlertLayout.inline,
leading: Badge(
tone: CoreBadgeTone.success,
showDot: true,
child: Text('Synced'),
),
title: 'All changes saved to the cloud.',
action: Icon(
LucideIcons.chevronDown,
iconStyle: CoreIconStyle(
size: CoreIconSize.size16,
color: CoreColor.token(CoreColors.onSurfaceSubtle),
),
),
)
action 슬롯의 아이콘은 caller 가 그리는 내용이라 색을 직접 지정합니다(onSurfaceSubtle).
텍스트만 (아이콘 없음)#
아이콘 없이 글과 톤만으로 안내하는 제품은 showIcon: false 로 앞자리를 끕니다. 제목이 이미
톤의 색(on{Tone}Container)이라 따로 적을 것이 없습니다 — 면과 제목 색이 톤을 나릅니다.
const Alert.success(
title: 'Transfer complete',
description: 'The money arrives within ten minutes.',
showIcon: false,
)
const Alert.success(
title: 'Transfer complete',
description: 'The money arrives within ten minutes.',
showIcon: false,
)
앱 전체에 적용하려면 같은 CoreAlertTheme 를 앱 루트 테마의 coreComponentTheme 에 둡니다.
ThemeData.copyWith 의 coreComponentTheme 인자는 Flutter 가 값을 돌려주는 함수, Web 이 값 자체를
받습니다.
동작 스펙 (Behavior)#
인터랙션#
- 닫기:
onDismiss가 제공되면 우측에 닫기 버튼 표시, 클릭 시 콜백 실행. 실제 숨김 처리는 부모 위젯이 담당. - 액션:
action은 닫기 버튼 왼쪽에 표시. 버튼 등 임의 위젯을 전달할 수 있습니다. -
아이콘: variant별 아이콘(info/success/warning/destructive)이 자동 표시됩니다.
leading을 넘기면 그 위젯이 아이콘을 대신합니다. - inline: 제목은 한 줄이며 넘치면 말줄임으로 잘리고,
description은 그리지 않습니다.
토큰#
시각 기본값은 위 CoreAlertStyle 표와 프로젝트 테마에서 해석합니다. 기본 보더 굵기는 0 이며, 보더를 원하면 색과 굵기를 함께 지정합니다. Alert 자체에는 표시 타이머 · 자동 제거 ·
Escape 닫힘 · 비활성 상태가 없습니다.
사용 가이드라인 (Usage Guidelines)#
✅ Do#
메시지의 심각도에 맞는 variant 사용
Alert.warning(
title: '주의',
description: '이 작업은 되돌릴 수 없습니다.',
)
색상과 아이콘이 심각도를 즉시 전달하여 사용자가 빠르게 인식합니다.
❌ Don't#
모든 안내 메시지에 destructive variant 사용 금지
// ❌ 일반 안내에 destructive 사용
Alert.destructive(
title: '새 버전 안내',
description: '새 버전이 출시되었습니다.',
)
잘못된 variant는 사용자에게 불필요한 긴장감을 주고 신뢰를 낮춥니다.
✅ Do#
일시적 피드백은 onDismiss로 처리
Alert.success(
title: '저장 완료',
description: '변경 사항이 저장되었습니다.',
onDismiss: handleDismiss,
)
사용자가 확인 후 직접 닫을 수 있어 화면 공간을 효율적으로 사용합니다.
❌ Don't#
긴 설명 텍스트를 description에만 모두 넣지 않기
// ❌ 너무 긴 description
Alert.warning(
title: '시스템 점검 안내',
description: '시스템 점검으로 인해 2024년 3월 15일 오전 2시부터 4시까지 서비스가 일시 중단됩니다. 이용에 불편을 드려 죄송합니다.',
)
title로 핵심 요약, description으로 보충 설명을 분리해야 스캔하기 쉽습니다.
접근성 (Accessibility)#
역할#
-
Web — 루트
<div>에 톤이 정한role이 붙고, 이름은aria-label로 실립니다. 닫기 버튼은 native<button type="button">에 로컬라이즈된aria-label을 답니다. -
Flutter —
CoUISemantics(role:, label:, container: true).alert과status는 이 플랫폼에서 둘 다Semantics(liveRegion: true)한 가지로 접힙니다. 닫기 버튼은Semantics(label:)로 감싸집니다.
| variant | Web role | Flutter |
|---|---|---|
destructive | alert | live region |
info / success / warning |
status |
live region |
neutral | 없음 | live region 아님 |
container: true 는 알림을 하나의 시맨틱 경계로 만듭니다. 다만 explicitChildNodes
를 켜지 않으므로 자체 경계를 갖지 않는 후손은 이 노드로 합쳐집니다 — 제목·본문 텍스트가
한 덩어리로 읽힐 수 있습니다. 버튼처럼 스스로 경계를 갖는 자식은 별도 노드로 남습니다.
키보드#
| 키 | Flutter | Web |
|---|---|---|
Tab | action 위젯 → 닫기 버튼 순으로 각각 tab stop | 동일 |
Enter / Space | 포커스된 버튼 활성화 | 포커스된 버튼 활성화 |
Escape 로 닫기 | 없음 | 없음 |
알림 상자 자체는 포커스를 받지 않습니다 — 양 플랫폼 모두 루트에 tabindex 나
Focus 를 두지 않습니다. 버튼 활성화는 Flutter 가 Clickable 의 Enter/Space
단축키를, Web 이 native <button> 의 브라우저 기본 동작을 탑니다.
스크린 리더#
이름은 필수 title 문자열입니다. 제목 생략용 기본 라벨 경로는 없습니다.
description 은 그 안의 텍스트 노드로 읽힙니다. inline 은 설명을 그리지 않고, 제목이
말줄임으로 잘려도 이름은 전체 title 입니다.
통보 긴급도는 톤이 정합니다 — 실패만 읽던 문장을 끊고(alert), 나머지 톤은
차례를 기다리며(status), 중립 variant 는 아무것도 통보하지 않습니다. 다만 이
세 단계는 Web 에서만 구분됩니다.
닫기 버튼은 양 플랫폼 모두 로컬라이즈된 이름을 갖습니다.
포커스 관리#
- 진입 — 없음. 알림은 오버레이가 아니라 인라인 상자라, 나타나도 포커스가 옮겨가지 않습니다.
-
이탈 — 없음.
onDismiss로 알림을 트리에서 걷어내면 닫기 버튼에 있던 포커스가 사라지고, 어디로 돌려놓을지는 호출자 몫입니다. - 트랩 — 없음.
알려진 제약#
-
Flutter 는 통보의 긴급도를 구분하지 못합니다.
alert과status가 같은 live region 하나로 접혀,destructive알림도 다른 톤과 똑같은 방식으로 통보됩니다. 미구현이 아니라 플랫폼에 통보 축이 하나뿐인 데서 오는 차이입니다. -
중립 variant 의 이름은 Web 에서 노출되지 않습니다. role 이 붙지 않아 루트가
generic 요소로 남고, generic 요소의
aria-label은 보조기술이 읽지 않습니다. 같은 조건에서 Flutter 는Semantics.label이 그대로 붙습니다. -
Web 루트의
aria-label은 caller 가 덮을 수 없습니다.attributes로 넘긴 값 위에 컴포넌트 값이 나중에 병합됩니다. 이름을 바꾸려면title을 씁니다 (role은configureAttributes를 거치므로 caller 가 덮을 수 있습니다). -
Dark 에서
neutralAlert 은surfaceBright면 위에서 경계가 사라집니다. Dark 의surfaceContainer와surfaceBright는 같은 색(neutral 900)이라, Dialog · Popup 같은 떠 있는 패널 위에 놓은neutralAlert 은 (stacked·inline모두) 면과 패널이 1.00:1 로 맞붙어 보입니다. 한 줄 상태 띠(inline)를 패널 안에 둘 때 가장 자주 만나는 자리입니다. 톤이 있는 variant(info/success/warning/destructive)는 자기 컨테이너 색을 가져 영향이 없고, Light 는 두 면이 다른 색이라 영향이 없습니다. 이 상태는 그대로 두고 기록만 합니다 — 패널 면이나 Alert 면을 바꾸는 것은 색 매핑의 결정입니다.
크로스 플랫폼 차이점 (Platform Differences)#
title / description / variant / layout / leading
/ onDismiss / action 은 양 플랫폼 동일 API 입니다. 아래는 플랫폼 고유 차이점만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| action · leading 타입 | Widget? | Component? |
| 콜백 타입 | VoidCallback? | CoreVoidCallback? |
| 통보 축 | live region 유무 (polite/assertive 구분 없음) | role="alert" / role="status" 로 급함까지 구분 |
| 닫기 버튼 영역 | 제목 줄 높이로 제약 | 제목 줄에 정렬하되 버튼은 줄 밖으로 넘쳐 원래 히트 영역 유지 |
leading 의 아이콘 테마 |
IconTheme |
IconTheme (같은 이름, 같은 색 · 크기). 아이콘이 아닌 내용은 두 플랫폼 모두 색을 받지 않습니다 |
| inline 제목 말줄임 | maxLines: 1 + TextOverflow.ellipsis |
truncate
(
overflow: hidden
·
text-overflow: ellipsis
·
white-space: nowrap
)
|
테마 덮기 (ThemeData.copyWith) |
coreComponentTheme: () => … — 값을 돌려주는 함수 |
coreComponentTheme: … — 값 |
actionButtonStyle 전달은 현재 Web의 Button 액션에만 적용되고 Flutter는 액션을 그대로 렌더합니다. 공통 스타일 슬롯의 배선 결손이며, 의도된 지원 차이로 취급하지 않습니다.
관련 컴포넌트 (Related Components)#
조합 예제#
// 폼 제출 후 결과 표시
Column(
children: [
if (submitError != null)
Alert.destructive(
title: '오류',
description: submitError!,
onDismiss: handleErrorDismissed,
),
// ... form fields
],
)