Alert#
다양한 상태(정보, 성공, 경고, 오류)를 시각적으로 구분하여 메시지를 표시하는 알림 영역 컴포넌트입니다. Flutter와 Web이 동일한 named properties API(title,
description, variant, onDismiss, action)를 사용합니다.
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'),
);
}
}
class AlertInfoExample extends StatefulComponent {
const AlertInfoExample({super.key});
@override
State<AlertInfoExample> createState() => _AlertInfoExampleState();
}
class _AlertInfoExampleState extends State<AlertInfoExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return Alert.info(
title: '정보',
description: '새로운 업데이트가 있습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertInfoExample extends StatefulWidget {
const AlertInfoExample({super.key});
@override
State<AlertInfoExample> createState() => _AlertInfoExampleState();
}
class _AlertInfoExampleState extends State<AlertInfoExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return Alert.info(
title: '정보',
description: '새로운 업데이트가 있습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertSuccessExample extends StatefulComponent {
const AlertSuccessExample({super.key});
@override
State<AlertSuccessExample> createState() => _AlertSuccessExampleState();
}
class _AlertSuccessExampleState extends State<AlertSuccessExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return Alert.success(
title: '저장 완료',
description: '변경 사항이 성공적으로 저장되었습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertSuccessExample extends StatefulWidget {
const AlertSuccessExample({super.key});
@override
State<AlertSuccessExample> createState() => _AlertSuccessExampleState();
}
class _AlertSuccessExampleState extends State<AlertSuccessExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return Alert.success(
title: '저장 완료',
description: '변경 사항이 성공적으로 저장되었습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertWarningExample extends StatefulComponent {
const AlertWarningExample({super.key});
@override
State<AlertWarningExample> createState() => _AlertWarningExampleState();
}
class _AlertWarningExampleState extends State<AlertWarningExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return Alert.warning(
title: '주의',
description: '이 작업은 되돌릴 수 없습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertWarningExample extends StatefulWidget {
const AlertWarningExample({super.key});
@override
State<AlertWarningExample> createState() => _AlertWarningExampleState();
}
class _AlertWarningExampleState extends State<AlertWarningExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return Alert.warning(
title: '주의',
description: '이 작업은 되돌릴 수 없습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertDestructiveExample extends StatefulComponent {
const AlertDestructiveExample({super.key});
@override
State<AlertDestructiveExample> createState() =>
_AlertDestructiveExampleState();
}
class _AlertDestructiveExampleState extends State<AlertDestructiveExample> {
bool _show = true;
@override
Component build(BuildContext context) {
if (_show) {
return Alert.destructive(
title: '오류 발생',
description: '요청을 처리하는 중 문제가 발생했습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = true),
child: const Text('Show alert'),
);
}
}
class AlertDestructiveExample extends StatefulWidget {
const AlertDestructiveExample({super.key});
@override
State<AlertDestructiveExample> createState() =>
_AlertDestructiveExampleState();
}
class _AlertDestructiveExampleState extends State<AlertDestructiveExample> {
bool _show = true;
@override
Widget build(BuildContext context) {
if (_show) {
return Alert.destructive(
title: '오류 발생',
description: '요청을 처리하는 중 문제가 발생했습니다.',
onDismiss: () => setState(() => _show = false),
);
}
return Button(
variant: CoreButtonVariant.outline,
onPressed: () => setState(() => _show = 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',
);
}
}
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'),
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 페이지 내 고정된 위치에서 중요한 상태 메시지를 안내할 때
- 폼 제출 결과(성공/오류)를 인라인으로 표시할 때
- 사용자가 반드시 인지해야 하는 경고나 안내를 표시할 때
대신 다른 컴포넌트를 사용하세요:
Toast: 일시적으로 나타났다 사라지는 짧은 알림에는 Toast 사용Banner: 페이지 최상단 전체 폭으로 중요 공지를 표시할 때 사용Dialog: 사용자의 확인 액션이 필요한 경고성 메시지에는 Dialog 사용
기본 사용법 (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: '요청을 처리하는 중 문제가 발생했습니다.',
)
// 기본 알림
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: '요청을 처리하는 중 문제가 발생했습니다.',
)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title |
String |
필수 | 알림 제목 — 이 컴포넌트가 반드시 갖는 한 줄 |
description |
String? |
null |
제목 아래 보조 설명 (선택) |
variant |
CoreAlertVariant |
defaultVariant |
알림 종류 (
defaultVariant
/
info
/
success
/
warning
/
destructive
)
|
onDismiss |
VoidCallback? (Flutter) / CoreVoidCallback? (Web) |
null |
닫기 버튼 클릭 핸들러. null이면 닫기 버튼 미표시 |
action |
Widget? (Flutter) / Component? (Web) |
null |
알림 우측에 표시할 액션 위젯 |
alertStyle |
CoreAlertStyle? |
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
Alert 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreAlertStyle 단일 슬롯으로 흐릅니다. 시맨틱 enum (variant) 과 behaviour (title
/ description / action / onDismiss) 는 위젯 파라미터로 직접 전달합니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (
variant,title,description,action,onDismiss) -
chrome / dimensional / 슬롯 스타일:
CoreAlertStyle한 곳으로 (backgroundColor/minHeight/borderRadius/padding/iconContentGapStyle/titleBottomMargin/titleStyle/descriptionStyle/iconStyle/actionButtonStyle/closeButtonStyle)
Resolve chain#
design system default for alert
→ CoreAlertTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.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
→ [defaultTitleColor]. Neutral on every variant by default — the icon carries the tone, so the title does not have to, and that is where its contrast headroom comes from. Stated as a field anyway: a caller who wants a toned title should not have to reach past the Style slot to get one.
|
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. |
minHeight |
double? |
Minimum box height (logical px). null → [defaultMinHeight]. |
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.
null
defers to [defaultIconContentGapStyle].
|
titleBottomMargin |
double? |
Bottom margin between the title and description (logical px).
null
defers to [defaultTitleBottomMargin].
|
titleStyle |
CoreTextStyle? |
Title text style override. |
descriptionStyle |
CoreTextStyle? |
Description text style override. |
iconStyle |
CoreIconStyle? |
Leading icon style override. |
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).
|
dismissIdleOpacity |
double? |
Deprecated since 0.131
— the dismiss glyph no longer dims. Was an idle-state opacity multiplier over the variant-tinted icon colour. An opacity times a tint resolves against whatever panel sits behind it, so no guard could check the result; a token can. Override
closeButtonStyle.foregroundColor
/
hoverForegroundColor
instead. Stated in the doc comment rather than left to the annotation alone: the docs-table generator skips annotations entirely, so a deprecation that lives only in
@Deprecated(...)
never reaches the published table.
|
CoreAlertStyle 변형별 기본값 (CoreAlertVariantStyle)#
| 필드 | neutral |
info |
success |
warning |
destructive |
|---|---|---|---|---|---|
backgroundColor |
surfaceContainer | infoContainer | successContainer | warningContainer | errorContainer |
descriptionColor |
onSurfaceVariant | onInfoContainer | onSuccessContainer | onWarningContainer | onErrorContainer |
iconColor |
onSurfaceVariant | onInfoContainer | onSuccessContainer | onWarningContainer | onErrorContainer |
제목은 variant 와 무관하게 항상 onSurface 입니다 — 톤은 아이콘과 본문이 나르고,
제목은 나르지 않습니다. 제목이 톤을 나르지 않는 덕분에 대비 여유가 생깁니다.
채움이 불투명한 것이 중요합니다. 이전 모델은 톤을 10 % 로 깔고 그 위에 같은 톤을
올려서, 라이트 모드 warning 이 1.71 까지 떨어졌습니다. 지금은 라벨이 페이지가 아니라
자기 패널을 배경으로 삼습니다.
레이아웃 — 아이콘은 타이틀 줄에 앉습니다#
Alert 은 최소 높이(minHeight, 72)를 갖고, 그 안에서 내용이 세로 중앙에
옵니다. 그 안쪽 행은 다시 위쪽 정렬이라, 아이콘과 닫기 표시가 타이틀의
line box 에 맞춰집니다.
두 박스인 이유가 있습니다. 하나로 합치면 둘 중 하나를 골라야 합니다 — 행을 중앙 정렬하면 아이콘이 제목과 본문 사이에 놓이고, 위쪽 정렬하면 제목만 있는 Alert 이 최소 높이가 열어둔 박스의 맨 위에 붙습니다. 두 경우가 동시에 맞는 배치는 이 구조뿐입니다.
아이콘 슬롯 높이는 토큰이 아니라 resolver 가 타이틀에서 파생합니다. 타이포가 바뀌면 따라옵니다.
사용 예 (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: '네트워크 연결을 확인해 주세요.',
)
동작 스펙 (Behavior)#
인터랙션#
- 닫기:
onDismiss가 제공되면 우측에 닫기 버튼 표시, 클릭 시 콜백 실행. 실제 숨김 처리는 부모 위젯이 담당. - 액션:
action은 닫기 버튼 왼쪽에 표시. 버튼 등 임의 위젯을 전달할 수 있습니다. - 아이콘: variant별 아이콘(info/success/warning/destructive)이 자동 표시됩니다.
토큰#
- Border radius:
CoreRadius.radius16(16px) - Border width:
CoreStrokeWidth.stroke1(1px) - Padding:
CoreSpace.space12세로 ×CoreSpace.space16가로 - Icon size:
CoreIconSize.size16(16px) - Icon gap:
CoreSpace.space12(12px) - 제목↔설명 여백:
CoreSpace.space4(4px)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
메시지의 심각도에 맞는 variant 사용
Alert.warning(
title: '주의',
description: '이 작업은 되돌릴 수 없습니다.',
)
색상과 아이콘이 심각도를 즉시 전달하여 사용자가 빠르게 인식합니다.
❌ Don't#
모든 안내 메시지에 destructive variant 사용 금지
// ❌ 일반 안내에 destructive 사용
Alert.destructive(
description: '새 버전이 출시되었습니다.',
)
잘못된 variant는 사용자에게 불필요한 긴장감을 주고 신뢰를 낮춥니다.
✅ Do#
일시적 피드백은 onDismiss로 처리
Alert.success(
title: '저장 완료',
description: '변경 사항이 저장되었습니다.',
onDismiss: handleDismiss,
)
사용자가 확인 후 직접 닫을 수 있어 화면 공간을 효율적으로 사용합니다.
❌ Don't#
긴 설명 텍스트를 description에만 모두 넣지 않기
// ❌ 너무 긴 description
Alert.warning(
description: '시스템 점검으로 인해 2024년 3월 15일 오전 2시부터 4시까지 서비스가 일시 중단됩니다. 이용에 불편을 드려 죄송합니다.',
)
title로 핵심 요약, description으로 보충 설명을 분리해야 스캔하기 쉽습니다.
접근성 (Accessibility)#
스크린 리더#
통보 방식은 variant 의 톤이 정합니다 — 모든 알림이 읽던 문장을 끊지는 않습니다.
| variant | Web | Flutter |
|---|---|---|
destructive | role="alert" (assertive) | live region |
info / success / warning |
role="status" (polite) |
live region |
defaultVariant | 역할 없음 | live region 아님 |
라벨은 title (없으면 로컬라이즈된 기본 알림 라벨) 이 채우고, Flutter 는 container: true 로 한 덩어리로 읽힙니다.
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | 닫기 버튼 / action 위젯으로 포커스 이동 |
Enter / Space | 포커스된 버튼 활성화 |
크로스 플랫폼 차이점 (Platform Differences)#
title / description / variant / onDismiss / action
은 양 플랫폼 동일 API 입니다. 아래는 플랫폼 고유 차이점만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| action 타입 | Widget? | Component? |
| 콜백 타입 | VoidCallback? | CoreVoidCallback? |
| 통보 축 | live region 유무 (polite/assertive 구분 없음) | role="alert" / role="status" 로 급함까지 구분 |
관련 컴포넌트 (Related Components)#
조합 예제#
// 폼 제출 후 결과 표시
Column(
children: [
if (submitError != null)
Alert.destructive(
title: '오류',
description: submitError!,
onDismiss: handleErrorDismissed,
),
// ... form fields
],
)