Vanilla JS를 사용하여, 사용자가 입력하는 순간 실시간으로 유효성을 검사해주도록 해보자.

먼저 회원가입 폼의 기본 HTML 구조를 만들자.
각 <input> 필드 바로 아래에 에러 메시지를 보여줄 <p class="error-message"> 태그를 배치한다.
<form action="/register" method="POST" class="register-form">
<div class="input-form">
<label for="userid">아이디</label>
<input type="text" id="userid" name="userId" required>
<p class="error-message" id="userid-error"></p>
</div>
<div class="input-form">
<label for="usernickname">닉네임</label>
<input type="text" id="usernickname" name="userNickname" required>
<p class="error-message" id="usernickname-error"></p>
</div>
<div class="input-form">
<label for="userpw">비밀번호</label>
<input type="password" id="userpw" name="userPw" autocomplete="off" required>
<p class="error-message" id="userpw-error"></p>
</div>
<button type="submit" class="submit-btn" disabled><strong>회원가입</strong></button>
</form>
제출 버튼은 초기에 disabled 속성으로 비활성화해두는 것이 좋음
에러 메시지가 평소에는 보이지 않고 공간도 차지하지 않게 display: none;으로 설정
이후 자바스크립트로 .show 클래스를 추가하여 display: block;으로 상태를 전환할 것.
.error-message {
display: none; /* 평소에는 숨김 */
color: #ed4956;
font-size: 12px;
margin-top: 4px;
}
.error-message.show {
display: block; /* .show 클래스가 붙으면 보이게 함 */
}
.submit-btn:disabled {
background-color: #b2dffc;
cursor: not-allowed;
}
자바스크립트 로직을 작성하자.
여기서 브라우저에 내장된 Constraint Validation API를 활용하면 코드를 훨씬 효율적으로 관리할 수 있다.
'')을 넣으면 유효함으로, 에러 메시지 문자열을 넣으면 유효하지 않음으로 상태가 설정된다.true 또는 false로 알려줌setCustomValidity로 설정한 상태를 그대로 반영true 또는 false를 반환form.checkValidity()가 폼 전체의 유효성을 판단할 수 있는 이유는, 개발자가 각 <input> 요소에 setCustomValidity()를 통해 브라우저의 내부 유효성 상태를 미리 업데이트해줬기 때문이다.
결론적으로, setCustomValidity는 쓰기 작업이고 checkValidity는 읽기 작업이라고 생각하면 된다.
코드에서 각 input의 상태를 직접 써줬기 때문에, form은 그 결과를 읽기만 하면 되는 것이다.
input 이벤트가 발생할 때마다 각 필드의 유효성을 검사하고, 그 결과를 커스텀 에러 메시지로 보여준다.
그리고 이벤트 위임(Event Delegation) 을 활용하여 form 태그에서 모든 input 이벤트를 감지하고, form.checkValidity()로 전체 폼의 유효성을 검사하여 버튼 상태를 업데이트한다.
// 각 입력 필드에 대한 유효성 검사 로직 (아이디, 닉네임, 비밀번호)
const idInput = document.getElementById('userid');
const idError = document.getElementById('userid-error');
idInput.addEventListener('input', (e) => {
let message = '';
// 아이디 유효성 검사 조건
if (e.target.value.length < 8 || e.target.value.length > 16) {
message = '비밀번호는 8자 이상 16자 이하로 입력해주세요.';
} else if (!/(?=.*[a-zA-Z])(?=.*\d)(?=.*[!@#$%^&*])/.test(e.target.value)) {
message = '비밀번호는 영문 대소문자, 숫자, 특수문자를 모두 포함해야 합니다.';
}
// 브라우저에 내장된 유효성 상태를 설정
e.target.setCustomValidity(message);
// 개발자가 만든 <p> 태그에 에러 메시지를 표시
idError.textContent = message;
// CSS 클래스로 메시지 보이기/숨기기 제어
message ? idError.classList.add('show') : idError.classList.remove('show');
});
...
const registerForm = document.querySelector('.register-form');
const submitBtn = document.querySelector('.submit-btn');
// 이벤트 위임을 이용한 버튼 활성화 로직
registerForm.addEventListener("input", () => {
// form.checkValidity()는 form 안의 모든 input 요소의 유효성을 검사
// 모든 필드가 유효하면 true, 하나라도 유효하지 않으면 false를 반환
const isFormValid = registerForm.checkValidity();
submitBtn.disabled = !isFormValid;
});
// 폼 제출 시, 브라우저의 기본 유효성 검증 UI가 뜨지 않도록 방지
registerForm.addEventListener('submit', (e) => {
if (!registerForm.checkValidity()) {
e.preventDefault(); // 폼 제출을 막고, 커스텀 메시지가 보이도록 유지
}
});
💡 왜 이벤트 위임을 사용?
input 요소 하나하나에 이벤트 리스너를 다는 대신, 부모인 form에 한 번만 달아도 이벤트 버블링 덕분에 모든 자식 input의 이벤트를 감지할 수 있다.
코드가 더 간결해지고 효율적이게 된다.
이렇게 HTML5의 표준 API + 커스텀 메세지 + 이벤트 위임 패턴을 활용하여 사용자 친화적인 실시간 유효성 검사 폼을 완성했다!