Form Field
입력 라벨, 설명, 오류, 폼 레벨 검증 메시지를 같은 방식으로 배선하는 필드/검증 패턴입니다.
마지막 업데이트 2026-07-02
AI / Consumer Implementation Contract#
AI 작업자와 소비 프로젝트는 FormField를 단순 래퍼가 아니라 라벨-컨트롤-메시지 배선 계약으로 취급합니다.
세부 설명보다 patterns/form-field.contract.json과 @wds/ui-web/testing의
assertFormFieldPatternCompliance(preset, screen)을 우선합니다.
MUST#
- 보이는 라벨 — 모든 표준 텍스트 입력은
FormField label을 사용하고, placeholder를 라벨로 대체하지 않습니다. - 컨트롤 배선 —
FormField안의 단일Input,Textarea,Select는id,aria-describedby,aria-invalid,aria-required가 연결되어야 합니다. - 필드 오류 위치 — 특정 필드의 오류는 컨트롤 바로 아래
FormField error로 둡니다. - 폼 오류 위치 — 한 필드에 귀속되지 않는 검증 실패는 CTA 바로 위
FormMessage variant="error"로 둡니다. - Primary CTA는 하나 — 폼 제출 영역 안의 primary action은 1개만 둡니다.
SHOULD#
- 라벨, 필드, 설명, 오류는 좌측 정렬을 유지합니다.
- 도움말은 짧고 실행 가능하게 씁니다.
- 필수 표시는 장식으로 두고, 실제 필수 상태는 컨트롤의
aria-required로 전달합니다.
NEVER#
- placeholder-only 입력을 만들지 않습니다.
aria-describedby로 연결되지 않은 오류 문구를 표시하지 않습니다.- 일반적인 인라인 검증을 Alert, Toast, 페이지 상단 배너로만 처리하지 않습니다.
- 인증 실패처럼 교차 필드 실패를 특정 필드 오류로 단정하지 않습니다.
프리셋별 구현 계약#
| 프리셋 | 반드시 있어야 하는 요소 | 절대 없어야 하는 요소 |
|---|---|---|
| 단일 필드 | FormField, 보이는 라벨, 하나의 Input/Textarea/Select, 선택적 설명 | placeholder-only 라벨, 분리된 라벨, 연결되지 않은 helper text |
| 필드 오류 | FormField error, role="alert", 컨트롤 aria-invalid, 오류 id를 참조하는 aria-describedby | 폼 레벨 오류를 필드 오류로 사용, role="alert" 없는 오류, 연결되지 않은 오류 문구 |
| 폼 레벨 오류 | 라벨된 필드들, FormMessage variant="error", primary submit CTA 1개 | Toast-only 검증, primary CTA 2개 이상, 인증 실패를 필드별 존재 여부 문구로 노출 |
React 소비 레시피#
import { Button, FormField, FormMessage, Input } from '@wds/ui-web';
export function FormFieldRecipe() {
return (
<form>
<FormField label="이메일" description="회사 메일을 입력하세요.">
<Input type="email" autoComplete="email" />
</FormField>
<Button type="submit">저장</Button>
</form>
);
}
import { FormField, Input } from '@wds/ui-web';
export function FieldErrorRecipe() {
return (
<FormField label="비밀번호" error="8자 이상이어야 합니다.">
<Input type="password" autoComplete="current-password" />
</FormField>
);
}
import { Button, FormField, FormMessage, Input } from '@wds/ui-web';
export function FormErrorRecipe() {
return (
<form>
<FormField label="이메일">
<Input type="email" autoComplete="email" />
</FormField>
<FormMessage variant="error">입력값을 확인하세요.</FormMessage>
<Button type="submit">저장</Button>
</form>
);
}