Validated | CoUI
LogoCoUI

Validated

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

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#

파라미터타입기본값설명
state CoreValidationState pending 유효성 상태 (pending / valid / invalid)
message Widget / Component? null 필드 아래 메시지 (pending 이면 숨김)
child Widget / Component? 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].

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

✅ 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)#

역할 / Semantics#

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

키보드#

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

포커스#

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

스크린 리더#

Validated 자체는 아무것도 알리지 않습니다. state 는 감싼 필드로 전파되지만(Flutter Data.inherit, Web ValidationStateScope) 양쪽 모두 hasError 색 계산에만 쓰입니다 — 필드에는 어떤 유효성 플래그도 붙지 않습니다. 메시지는 ValidatorHint 로 렌더되고 심각도 아이콘은 양 플랫폼 모두 접근성 트리에서 제외되므로(ExcludeSemantics / aria-hidden="true"), 리더가 그 지점을 지나갈 때 메시지 문장은 일반 텍스트로 읽힙니다. 다만 메시지가 나타나는 순간에는 아무것도 안내되지 않습니다.

알려진 제약#

  • 감싼 필드에 aria-invalid(Web) 나 대응하는 Semantics 플래그(Flutter) 가 설정되지 않습니다 — 유효성은 색 변화와 형제 메시지로만 표현됩니다.
  • 메시지가 aria-describedby 로 필드와 연결되지 않고 live region 도 아니라서, 제출 후 뒤늦게 나타나는 에러는 안내되지 않습니다.
  • 실패한 필드로 포커스를 옮기지 않습니다.
  • state: pending 은 메시지를 조용히 숨깁니다.

폼 제출 검증에 쓴다면 필드의 invalid 상태 노출, 메시지의 live region 처리, 실패 시 포커스 이동은 소비자가 직접 붙여야 합니다.

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