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

Validated

폼 필드에 유효성 상태를 전파하고 메시지를 표시하는 래퍼

목차

폼 필드를 감싸 유효성 state 를 필드 자신에게 전파하고 아래 message 를 표시하는 프레젠테이션 래퍼입니다. invalid 상태는 감싼 필드(예: TextField)의 자체 error variant 를 작동시켜 필드의 보더와 포커스 링이 에러 톤으로 바뀝니다 — 별도의 겹 링을 그리지 않습니다. pending 상태에서는 메시지가 숨겨지고, invalid / valid 메시지는 ValidatorHint 로 렌더됩니다. Flutter / Web 1:1 동일 API 입니다.

Live Preview#

사용법#

Validated(
  state: CoreValidationState.invalid,
  message: const Text('필수 항목입니다.'),
  child: TextField(placeholder: const Text('이름')),
)

빠른 오버라이드 (Chain)#

이미 만든 Validated 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class ValidatedChainExample extends StatelessWidget {
  const ValidatedChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 320,
      child:
          Validated(
            state: CoreValidationState.invalid,
            message: const Text('필수 항목입니다.'),
            child: TextField(placeholder: const Text('이름')),
          ).withStyle(
            const CoreValidatedStyle(
              fieldMessageGapStyle: CoreGapStyle(size: CoreSpace.space16),
              validatorHintStyle: CoreValidatorHintStyle(
                accentColor: CoreColor.token(CoreColors.warning),
                iconStyle: CoreIconStyle(size: CoreIconSize.size16),
                iconLabelGapStyle: CoreGapStyle(size: CoreSpace.space8),
              ),
            ),
          ),
    );
  }
}
class ValidatedChainExample extends StatelessComponent {
  const ValidatedChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return div(
      styles: Styles(raw: {'width': '320px'}),
      [
        Validated(
          state: CoreValidationState.invalid,
          message: Text('필수 항목입니다.'),
          child: TextField(placeholder: Text('이름')),
        ).withStyle(
          const CoreValidatedStyle(
            fieldMessageGapStyle: CoreGapStyle(size: CoreSpace.space16),
            validatorHintStyle: CoreValidatorHintStyle(
              accentColor: CoreColor.token(CoreColors.warning),
              iconStyle: CoreIconStyle(size: CoreIconSize.size16),
              iconLabelGapStyle: CoreGapStyle(size: CoreSpace.space8),
            ),
          ),
        ),
      ],
    );
  }
}

Props / Parameters#

W는 합성 슬롯 타입이며 Flutter에서는 Widget, Web에서는 Component입니다.

파라미터타입기본값설명
state CoreValidationState pending 유효성 상태 (pending / valid / invalid)
message W? null 필드 아래 메시지 (pending 이면 숨김)
childW?null감싸는 폼 필드
validatedStyle CoreValidatedStyle? null chrome / dimensional 단일 진입점

스타일 시스템 (Style System)#

CoreValidatedStyle 필드#

필드타입설명
validatorHintStyle CoreValidatorHintStyle? Override ring colour applied around the wrapped child. Nested [CoreValidatorHintStyle] slot for the composed message ValidatorHint . Raw-forwarded on top of [defaultValidatorHintStyle].
fieldMessageGapStyle CoreGapStyle? Gap style between the wrapped child and the message — nested [CoreGapStyle] slot forwarded to the Gap widget that separates the field from the message. null defers to [defaultFieldMessageGapStyle].

동작 스펙 (Behavior)#

Validated는 검증 함수를 실행하거나 상태를 계산하지 않고 호출자가 전달한 state를 표시합니다. 자식이 있으면 주변 유효성 상태를 전파하고, pending이면 메시지를 숨깁니다. 메시지가 있고 상태가 invalid이면 error 톤, valid이면 success 톤의 ValidatorHint를 합성합니다. 별도 링을 덧그리지 않으며 필드와 메시지 간격은 중첩 CoreGapStyle을 따릅니다.

사용 가이드라인 (Usage Guidelines)#

필드와 메시지를 함께 묶고 유효성 상태를 전파하려면 Validated를, 메시지 한 줄만 표시하려면 ValidatorHint를 사용합니다. 개별 검증 규칙의 pending/valid/invalid 표시에는 ValidationBadge가 맞습니다. 저장·로드 요청 실패처럼 필드 값 이외의 실패는 Alert로 안내합니다.

✅ Do#

필드 자체의 에러 표현은 state 전파에 맡기기

Validated(
  state: isSubmitting
      ? CoreValidationState.pending
      : (hasError ? CoreValidationState.invalid : CoreValidationState.valid),
  message: Text(errorText ?? '입력값이 확인되었습니다.'),
  child: TextField(placeholder: const Text('이름')),
)

invalid 상태는 Data.inherit 로 감싼 TextField 에 전파되어 필드가 스스로 error variant(보더 + 포커스 링)를 적용합니다 — Validated 는 별도 링을 그리지 않습니다.


❌ Don't#

필드에 에러 색을 직접 또 지정하지 않기

// ❌ state 가 이미 전파하는 에러 표현을 필드에도 중복 지정
Validated(
  state: CoreValidationState.invalid,
  child: TextField(
    textFieldStyle: CoreTextFieldStyle(
      borderColor: CoreColor.token(CoreColors.error),
    ),
  ),
)

state 가 이미 필드의 error variant 를 켜므로, 여기에 색을 또 지정하면 두 값 중 어느 쪽이 이기는지 불명확해지고 같은 로직을 두 곳에서 유지하게 됩니다.

접근성 (Accessibility)#

역할#

어느 플랫폼도 역할을 내보내지 않습니다. Flutter 의 build 는 순수 Column 을 반환하고 Semantics 를 한 번도 호출하지 않으며, Web 은 role / aria-invalid / aria-describedby / aria-live 중 무엇도 붙지 않은 평범한 <div> 입니다.

키보드#

처리하는 키가 없습니다 — 자식과 Gap, ValidatorHint 를 합성하기만 합니다.

포커스 관리#

포커스를 관리하지 않습니다. 유효하지 않은 필드로 포커스를 옮기지도, 트랩하거나 복원하지도 않습니다.

스크린 리더#

Validated 자체에는 live region이 없지만 합성된 ValidatorHint는 톤에 맞게 메시지의 등장을 통보합니다. Web은 invalid 메시지에 role="alert", valid 메시지에 role="status"를 사용하고 Flutter는 live region으로 노출합니다. 심각도 아이콘은 접근성 트리에서 제외됩니다. 주변 상태 전파만으로 필드와 메시지의 설명 관계가 자동 연결되지는 않습니다.

알려진 제약#

  • 감싼 필드에 aria-invalid(Web) 나 대응하는 Semantics 플래그(Flutter) 가 설정되지 않습니다 — 유효성은 색 변화와 형제 메시지로만 표현됩니다.
  • 메시지가 aria-describedby로 필드와 자동 연결되지 않습니다. 메시지 등장 통보와 필드에 포커스했을 때의 설명 연결은 별개입니다.
  • 실패한 필드로 포커스를 옮기지 않습니다.
  • state: pending 은 메시지를 조용히 숨깁니다.

폼 제출 검증에 쓴다면 필드의 invalid 상태 노출, 설명 연결, 실패 시 포커스 이동을 소비자가 확인해야 합니다.

상태를 색으로 구분하는 만큼 강제 색상 모드가 특히 중요합니다 — 고대비 · 강제 색상처럼 전 컴포넌트에 공통으로 걸리는 축은 전역 접근성 축에서 다룹니다.

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

항목FlutterWeb
주변 유효성 상태 Data.inherit<CoreValidationState> ValidationStateScope
필드·메시지 배치 Column + Gap 루트 <div> + Gap
합성 메시지 통보ValidatorHint의 live regionValidatorHint의 alert/status 역할
필드 설명 연결호출자가 필드 시맨틱 구성호출자가 aria-describedby와 메시지 id 배선