ValidationBadge | CoUI
본문으로 건너뛰기
LogoCoUI

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#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 비밀번호 설정 시 각 조건(길이, 대문자, 숫자 포함 등)의 충족 여부를 실시간으로 표시할 때
  • 아이디, 이메일 중복 확인 결과를 시각적으로 안내할 때
  • 폼 필드 아래에 여러 검증 규칙을 체크리스트 형태로 나열할 때
  • 입력 진행 중 실시간 유효성 상태를 사용자에게 보여줄 때

대신 다른 컴포넌트를 사용하세요:

  • Status: 시스템이나 사용자 상태(온라인/오프라인)를 표시할 때
  • Banner 또는 Toast: 전체 폼 제출 후 오류 메시지를 표시할 때
  • Text: 단순한 에러 메시지 텍스트만 필요할 때

Props / Parameters#

속성타입기본값설명
labelString필수표시할 검증 조건 텍스트
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)#

인터랙션#

  • 표시 전용. 인터랙션 없음.
  • state prop 변경 시 색상/아이콘 즉시 업데이트.

상태 전환#

  • 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 은 caller attributes 뒤에 병합되어 caller 가 바꿀 수 없고, 나머지 aria-* 는 그대로 통과합니다.

키보드#

없음. 표시 전용이라 포커스를 받지 않고 처리하는 키도 없습니다.

스크린 리더#

  • 이름 — label 문자열입니다. Flutter 는 자식 Text(label), Web 은 라벨 <span> 의 텍스트 노드에서 옵니다.
  • 통보 — 양 플랫폼 모두 live region 이라 state 가 바뀌어 다시 그려지면 읽힙니다 (Flutter liveRegion: true, Web role="status").
  • 상태 아이콘에는 텍스트 대체가 없습니다. Flutter 의 Icon 은 semanticsLabel 없이 SvgPicture 를 그리고, Web 의 <iconify-icon> 에는 aria-label 도 aria-hidden 도 붙지 않습니다. 그래서 리더가 읽는 것은 규칙 문구뿐이고 통과 / 실패 / 대기라는 상태 자체는 이름에 들어가지 않습니다 — valid 와 invalid 가 같은 문장으로 읽힙니다.

포커스 관리#

없음. 오버레이가 아니고 포커스 가능 요소도 아니라 진입·이탈·트랩이 모두 해당되지 않습니다.

알려진 제약#

  • 상태를 이름에 넣으려면 label 에 직접 씁니다 — ValidationBadge(label: '8자 이상 — 충족', state: .valid). Web 은 attributes: {'aria-label': ...} 로 루트 이름을 덮을 수도 있지만(caller attributes 는 role 을 빼고 그대로 통과), Flutter 에는 대응하는 진입점이 없어 label 이 유일한 경로입니다.
  • live region 을 끄는 파라미터가 없습니다. 배지를 나열한 폼에서 조건들이 한 번에 갱신되면 배지 수만큼 통보됩니다 — 각 배지가 자기 루트에 live region 을 답니다. 대안: 한 화면에 두는 배지 수를 줄입니다(위 가이드라인의 5~6개 권장과 같은 이유입니다).

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명ValidationBadgeValidationBadge
색상 colorScheme.success/error + scaleAlpha(0.1/0.2/0.6) bg-${cs.success}/10 등 Tailwind alpha (Card 패턴)
아이콘 Icon(LucideIcons.*) Icon(LucideIcons.*) (동일 토큰)
  • 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,
    ),
  ],
)