ValidationBadge
입력값의 유효성 검증 결과를 아이콘과 텍스트로 표시하는 배지 컴포넌트
목차
폼 필드의 유효성 검증 결과를 아이콘과 텍스트로 시각적으로 표시하는 배지 컴포넌트입니다. 비밀번호 강도, 이메일 형식 등 검증 상태 안내에 적합합니다.
기본 사용법 (Basic Usage)#
// 유효한 상태 (기본)
ValidationBadge(
label: '8자 이상',
state: CoreValidationState.valid,
)
// 무효한 상태
ValidationBadge(
label: '숫자 포함',
state: CoreValidationState.invalid,
)
// 입력 전(pending) 상태
ValidationBadge(
label: '대문자 포함',
state: CoreValidationState.pending,
)
// bool로 간편 생성 (true → valid, false → invalid)
ValidationBadge.fromBool(
label: '대문자 포함',
isValid: hasUpperCase,
)
// hasInput → !hasInput 이면 pending, 그 외엔 isValid 기준
ValidationBadge.withPending(
label: '숫자 포함',
hasInput: password.isNotEmpty,
isValid: hasNumber,
)
// 유효한 상태 (기본)
ValidationBadge(
label: '8자 이상',
state: CoreValidationState.valid,
)
// 무효한 상태
ValidationBadge(
label: '숫자 포함',
state: CoreValidationState.invalid,
)
// 입력 전(pending) 상태
ValidationBadge(
label: '대문자 포함',
state: CoreValidationState.pending,
)
// bool로 간편 생성
ValidationBadge.fromBool(
label: '대문자 포함',
isValid: hasUpperCase,
)
// hasInput 기반 3-state factory
ValidationBadge.withPending(
label: '숫자 포함',
hasInput: password.isNotEmpty,
isValid: hasNumber,
)
Live Preview#
코드 보기
class ValidationBadgeDefaultExample extends StatelessComponent {
const ValidationBadgeDefaultExample({super.key});
@override
Component build(BuildContext context) {
return const ValidationBadge(
label: 'At least 8 characters',
state: CoreValidationState.valid,
);
}
}
class ValidationBadgeDefaultExample extends StatelessWidget {
const ValidationBadgeDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return const ValidationBadge(
label: 'At least 8 characters',
state: CoreValidationState.valid,
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 비밀번호 설정 시 각 조건(길이, 대문자, 숫자 포함 등)의 충족 여부를 실시간으로 표시할 때
- 아이디, 이메일 중복 확인 결과를 시각적으로 안내할 때
- 폼 필드 아래에 여러 검증 규칙을 체크리스트 형태로 나열할 때
- 입력 진행 중 실시간 유효성 상태를 사용자에게 보여줄 때
대신 다른 컴포넌트를 사용하세요:
Status: 시스템이나 사용자 상태(온라인/오프라인)를 표시할 때Banner또는Toast: 전체 폼 제출 후 오류 메시지를 표시할 때Text: 단순한 에러 메시지 텍스트만 필요할 때
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
label | String | 필수 | 표시할 검증 조건 텍스트 |
labelMaxLines |
int? |
null |
라벨이 넘칠 때까지 허용하는 줄 수. null 은 기존처럼 한 줄로 자릅니다 |
labelOverflow |
CoreTextOverflow? |
null |
labelMaxLines
에 도달한 라벨의 넘침 처리 (
clip
/
fade
/
ellipsis
/
visible
).
null
은 기존처럼 말줄임표(
ellipsis
)입니다
|
state |
CoreValidationState |
필수 | 검증 상태 (pending / valid / invalid) |
pendingIcon |
IconName? |
LucideIcons.clock |
pending 상태에서 표시할 아이콘 override |
validIcon |
IconName? |
LucideIcons.check |
valid 상태에서 표시할 아이콘 override |
invalidIcon |
IconName? |
LucideIcons.x |
invalid 상태에서 표시할 아이콘 override |
size |
CoreValidationBadgeSize? |
small (token default) |
크기 토큰 (extraSmall / small / medium) |
style |
CoreValidationBadgeStyle? |
null |
상태별 색상 override (background/foreground/border × 3 state = 9 필드, CoreColor) |
스타일 시스템 (Style System)#
CoreValidationBadgeStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
pendingBackgroundColor |
CoreColor? |
Pending background color override. |
pendingLabelTextStyle |
CoreTextStyle? |
Pending label text style override. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw
pendingForegroundColor
field removed).
null
defers to [defaultPendingLabelTextStyle] over the per-size role from [defaultsBySize]
[size].labelStyle
.
|
pendingBorderColor |
CoreColor? |
Pending border color override. |
validBackgroundColor |
CoreColor? |
Valid background color override. |
validLabelTextStyle |
CoreTextStyle? |
Valid label text style override. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw
validForegroundColor
field removed).
null
defers to [defaultValidLabelTextStyle] over the per-size role from [defaultsBySize]
[size].labelStyle
.
|
validBorderColor |
CoreColor? |
Valid border color override. |
invalidBackgroundColor |
CoreColor? |
Invalid background color override. |
invalidLabelTextStyle |
CoreTextStyle? |
Invalid label text style override. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw
invalidForegroundColor
field removed).
null
defers to [defaultInvalidLabelTextStyle] over the per-size role from [defaultsBySize]
[size].labelStyle
.
|
invalidBorderColor |
CoreColor? |
Invalid border color override. |
iconLabelGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the icon ↔ label gap. Forwarded straight to
Gap(gapStyle: …)
(Flutter) / reflected as the inline
gap
rem rule (Web).
null
defers to [CoreValidationBadgeStyle.defaultsBySize]
[size].iconLabelGapStyle
.
|
iconStyle |
CoreIconStyle? |
Nested [CoreIconStyle] slot for the state icon — forwarded to
Icon(iconStyle: …)
.
null
defers to the resolver-built base whose
size
comes from [CoreValidationBadgeStyle.defaultsBySize]
[size].iconSize
and whose
color
is the resolved per-state foreground (sb #2305 — flat
iconSize
+
iconColor
folded into one nested slot, mirroring the Web resolver's
iconStyle
). No paired
default*
: every value it would carry varies on an axis a
static const
cannot hold —
size
per size preset, and
color
per state on Flutter but deliberately absent on Web (the icon inherits the chip's colour). The slot is an overlay on the base each resolver composes, not a value with a baseline of its own.
|
borderWidth |
double? |
Border stroke width override (logical px). Overrides [defaultBorderWidth] when set. |
빠른 오버라이드 (Chain)#
이미 만든 ValidationBadge 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class ValidationBadgeChainExample extends StatelessWidget {
const ValidationBadgeChainExample({super.key});
@override
Widget build(BuildContext context) {
return const ValidationBadge(
label: 'At least 8 characters',
state: CoreValidationState.valid,
).withStyle(
const CoreValidationBadgeStyle(
validBackgroundColor: CoreColor.token(
CoreColors.primary,
opacity: CoreOpacity.opacity10,
),
validBorderColor: CoreColor.token(CoreColors.primary),
validLabelTextStyle: CoreTextStyle.token(
CoreTextStyles.labelMedium,
color: CoreColor.token(CoreColors.primary),
),
borderWidth: CoreStrokeWidth.stroke2,
iconStyle: CoreIconStyle(
size: CoreIconSize.size16,
color: CoreColor.token(CoreColors.primary),
),
iconLabelGapStyle: CoreGapStyle(size: CoreSpace.space8),
),
);
}
}
class ValidationBadgeChainExample extends StatelessComponent {
const ValidationBadgeChainExample({super.key});
@override
Component build(BuildContext context) {
return const ValidationBadge(
label: 'At least 8 characters',
state: CoreValidationState.valid,
).withStyle(
const CoreValidationBadgeStyle(
validBackgroundColor: CoreColor.token(
CoreColors.primary,
opacity: CoreOpacity.opacity10,
),
validBorderColor: CoreColor.token(CoreColors.primary),
validLabelTextStyle: CoreTextStyle.token(
CoreTextStyles.labelMedium,
color: CoreColor.token(CoreColors.primary),
),
borderWidth: CoreStrokeWidth.stroke2,
iconStyle: CoreIconStyle(
size: CoreIconSize.size16,
color: CoreColor.token(CoreColors.primary),
),
iconLabelGapStyle: CoreGapStyle(size: CoreSpace.space8),
),
);
}
}
변형 (Variants)#
Default (valid)#
가장 흔한 success 상태. success container 배경 + check 아이콘.
ValidationBadge(
label: '8자 이상',
state: CoreValidationState.valid,
)
Pending#
입력 전 중립 상태. surface container 배경 + 옅은 onSurface 텍스트 + clock 아이콘.
ValidationBadge(
label: '대문자 포함',
state: CoreValidationState.pending,
)
Invalid#
실패 상태. error container 배경 + X 아이콘.
ValidationBadge(
label: '숫자 포함',
state: CoreValidationState.invalid,
)
Medium size#
typography 가 한 단계 크고 padding 도 더 넉넉한 변형.
ValidationBadge(
label: '대문자 포함',
state: CoreValidationState.valid,
size: CoreValidationBadgeSize.medium,
)
크기 (Sizes)#
높이는 고정이고 내용은 그 안에서 세로 가운데에 놓입니다. 테두리는 기본으로 그리지 않습니다(borderWidth 기본 0) — 채움 · 아이콘 · 글자로 상태를 말합니다.
| 크기 | 토큰 | 높이 | 좌우 padding | 아이콘-글자 간격 | radius | icon | 글자 역할 |
|---|---|---|---|---|---|---|---|
| Extra Small | CoreValidationBadgeSize.extraSmall |
20 | space8 |
space4 |
radius4 |
space12 |
labelXs (11) |
| Small (default) | CoreValidationBadgeSize.small |
24 | space8 |
space4 |
radius6 |
space12 |
labelSmall (12) |
| Medium | CoreValidationBadgeSize.medium |
28 | space10 |
space6 |
radius6 |
space14 |
labelMedium (13) |
동작 스펙 (Behavior)#
인터랙션#
- 표시 전용. 인터랙션 없음.
stateprop 변경 시 색상/아이콘 즉시 업데이트.
상태 전환#
pending→valid: 회색 배경 + clock 아이콘 → success container 배경 + check.valid→invalid: success container → error container 배경 + X.
사용 가이드라인 (Usage Guidelines)#
개별 검증 규칙의 pending/valid/invalid 상태를 작은 배지로 표시하려면 ValidationBadge를 사용합니다. 메시지 한 줄은 ValidatorHint, 필드와 메시지의 상태 전파는 Validated, 검증 이외의 상태 라벨은 Badge를 선택합니다. 배지는 검증을 실행하지 않고 전달된 state를 표시합니다.
✅ Do#
실시간 검증 + withPending로 미입력 단계는 중립 표시
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ValidationBadge.withPending(
label: '8자 이상',
hasInput: password.isNotEmpty,
isValid: password.length >= 8,
),
ValidationBadge.withPending(
label: '대문자 포함',
hasInput: password.isNotEmpty,
isValid: hasUpperCase,
),
],
)
입력 시작 전엔 pending(중립), 시작 후엔 valid/invalid 로 전환됩니다.
❌ Don't#
입력 전부터 모든 조건을 invalid 로 표시 금지
// ❌ 입력 전부터 모든 조건이 빨간 X
ValidationBadge(label: '8자 이상', state: CoreValidationState.invalid)
ValidationBadge(label: '대문자', state: CoreValidationState.invalid)
아직 입력 안 된 상태에서 모두 실패로 표시되면 위압감을 줍니다. pending 상태 또는 withPending factory 를 사용하세요.
✅ Do#
긍정적 메시지로 조건 표현
ValidationBadge.fromBool(label: '영문 대문자 포함', isValid: hasUpperCase)
ValidationBadge.fromBool(label: '숫자 1개 이상', isValid: hasNumber)
"대문자 미포함"보다 "영문 대문자 포함"처럼 충족해야 할 조건을 긍정형으로 표현하면 더 명확합니다.
❌ Don't#
너무 많은 조건을 한 번에 표시 금지
5~6개 이내로 핵심 조건만. 그 이상은 사용자에게 부담.
접근성 (Accessibility)#
역할#
-
Flutter —
Semantics(liveRegion: true). 역할 플래그는 세우지 않고 live region 만 걸며, 이름은 자식Text(label)에서 옵니다. -
Web — 루트
<span>에role="status"(암묵적aria-live="polite"). 이role은 callerattributes뒤에 병합되어 caller 가 바꿀 수 없고, 나머지aria-*는 그대로 통과합니다.
키보드#
없음. 표시 전용이라 포커스를 받지 않고 처리하는 키도 없습니다.
스크린 리더#
-
이름 —
label문자열입니다. Flutter 는 자식Text(label), Web 은 라벨<span>의 텍스트 노드에서 옵니다. -
통보 — 양 플랫폼 모두 live region 이라
state가 바뀌어 다시 그려지면 읽힙니다 (FlutterliveRegion: true, Webrole="status"). -
상태 아이콘에는 텍스트 대체가 없습니다. Flutter 의
Icon은semanticsLabel없이SvgPicture를 그리고, Web 의<iconify-icon>에는aria-label도aria-hidden도 붙지 않습니다. 그래서 리더가 읽는 것은 규칙 문구뿐이고 통과 / 실패 / 대기라는 상태 자체는 이름에 들어가지 않습니다 —valid와invalid가 같은 문장으로 읽힙니다.
포커스 관리#
없음. 오버레이가 아니고 포커스 가능 요소도 아니라 진입·이탈·트랩이 모두 해당되지 않습니다.
알려진 제약#
-
상태를 이름에 넣으려면
label에 직접 씁니다 —ValidationBadge(label: '8자 이상 — 충족', state: .valid). Web 은attributes: {'aria-label': ...}로 루트 이름을 덮을 수도 있지만(callerattributes는role을 빼고 그대로 통과), Flutter 에는 대응하는 진입점이 없어label이 유일한 경로입니다. - live region 을 끄는 파라미터가 없습니다. 배지를 나열한 폼에서 조건들이 한 번에 갱신되면 배지 수만큼 통보됩니다 — 각 배지가 자기 루트에 live region 을 답니다. 대안: 한 화면에 두는 배지 수를 줄입니다(위 가이드라인의 5~6개 권장과 같은 이유입니다).
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | ValidationBadge | ValidationBadge |
| 색상 | colorScheme.success/error + scaleAlpha(0.1/0.2/0.6) |
bg-${cs.success}/10 등 Tailwind alpha (Card 패턴) |
| 아이콘 | Icon(LucideIcons.*) |
Icon(LucideIcons.*) (동일 토큰) |
관련 컴포넌트 (Related Components)#
- TextField: ValidationBadge 와 함께 폼 필드 검증 UI 구성.
- Form: 전체 폼 수준의 검증 관리.
- Status: 시스템/사용자 상태 표시 (ValidationBadge 와 구분).
조합 예제#
// ValidationBadge + Input 조합으로 비밀번호 설정 UI
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Input(label: '새 비밀번호', obscureText: true, onChanged: handlePasswordChange),
Gap(size: CoreSpace.space12),
ValidationBadge.withPending(
label: '8자 이상',
hasInput: password.isNotEmpty,
isValid: password.length >= 8,
),
ValidationBadge.withPending(
label: '영문 대문자 1자 이상',
hasInput: password.isNotEmpty,
isValid: hasUpperCase,
),
ValidationBadge.withPending(
label: '숫자 1자 이상',
hasInput: password.isNotEmpty,
isValid: hasNumber,
),
],
)