본문으로 건너뛰기
개발 머꼬
개발 노트HTML
hohyeon.dev20

폼 검증을 JS로 다 만들다가 브라우저가 이미 하고 있던 일을 발견한 과정

  • #Accessibility
  • #Engineering Note
  • #Forms
  • #HTML

문제 발생

이메일 형식과 필수 입력을 검사하는 코드를 직접 만들었습니다. 필드가 늘 때마다 이 로직도 함께 늘었습니다.

if (!email.value) setError(email, "필수 항목입니다");
else if (!/^[^@]+@[^@]+$/.test(email.value)) setError(email, "이메일 형식이 아닙니다");

브라우저 기본 검증을 쓰려고 :invalid로 스타일을 줘 봤지만, 아무것도 입력하지 않은 상태에서 폼 전체가 빨갛게 표시돼서 쓸 수 없었습니다.

input:invalid { border-color: red; }   /* 페이지를 열자마자 빨갛다 */

원인 분석

브라우저에는 제약 검증(Constraint Validation) 이 이미 들어 있습니다. type="email", required, minlength, pattern 같은 속성만 적으면 검증과 오류 메시지를 브라우저가 처리합니다.

문제는 :invalid의 시점이었습니다. 요소가 유효하지 않으면 즉시 매칭되므로, required인 빈 입력은 페이지가 열린 순간부터 유효하지 않습니다. 사용자는 아직 아무것도 하지 않았는데 틀렸다고 표시받습니다.

:user-invalid는 여기에 사용자 상호작용이라는 조건을 더합니다. 사용자가 폼을 제출하려 했거나 그 필드를 건드리고 떠난 뒤부터 매칭됩니다. 2023년 11월부터 Baseline입니다.

해결 방안

  1. 검증 규칙은 HTML에 적고, 표시는 :user-invalid로 합니다.
<label for="email">이메일 *</label>
<input id="email" name="email" type="email" required />
<span class="hint"></span>
input:user-invalid { border-color: var(--danger); }
input:user-invalid + .hint::before { content: "올바른 이메일을 입력해 주세요"; }
input:user-valid { border-color: var(--success); }
  1. 메시지 문구는 setCustomValidity로 바꿉니다. 브라우저 기본 문구가 어색하면 교체할 수 있고, 빈 문자열로 되돌리면 다시 유효해집니다.
password.addEventListener("input", () => {
  password.setCustomValidity(
    password.value.length < 8 ? "비밀번호는 8자 이상이어야 합니다" : "",
  );
});
  1. 서버 검증을 대신하지 않습니다. 이건 UX 장치일 뿐입니다. HTML 속성은 개발자 도구로 지울 수 있고, 요청은 브라우저를 거치지 않고도 보낼 수 있습니다. 이 저장소도 폼 필드의 required/minlength는 힌트로만 두고 실제 판단은 서버의 Zod 스키마가 합니다.
  2. 기본 말풍선이 방해되면 novalidate + checkValidity()로 직접 제어합니다. 검증 로직은 브라우저 것을 그대로 쓰면서 표시만 우리가 맡습니다.
form.addEventListener("submit", (event) => {
  if (!form.checkValidity()) {
    event.preventDefault();
    showOwnMessages();
  }
});
  1. :user-invalid가 없는 환경을 고려합니다. 미지원 브라우저에서는 해당 규칙이 무시되어 오류 표시만 나오지 않습니다 — 폼은 그대로 동작하므로 점진적 향상으로 안전합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.