사용자가 긴 양식을 한 번에 작성하도록 하면 이탈률이 높아지기 쉽습니다. 이럴 때 다단계(Multi-Step) 폼을 활용하면 입력 항목을 여러 단계로 나누어 사용자 경험을 크게 개선할 수 있습니다. 이번 글에서는 순수 JavaScript만으로 단계별 등록 폼을 만드는 방법을 예제 코드와 함께 자세히 살펴보겠습니다.
다단계 폼의 동작 원리
이 예제는 크게 세 부분으로 구성됩니다.
- 단계별 페이지 분리 — 각 단계는
page클래스를 가진<div>로 구성되며, 기본적으로display: none으로 숨겨집니다. - 단계 전환 로직 — [이전]/[다음] 버튼 클릭 시 현재 단계를 숨기고 다음(또는 이전) 단계를 표시합니다.
- 유효성 검사 및 진행 표시기 — 각 단계의 입력값이 비어 있는지 검사하고, 하단의 원형 인디케이터로 진행 상황을 시각적으로 보여줍니다.
전체 예제 코드
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
box-sizing: border-box;
}
body {
background-color: #f1f1f1;
}
#regForm {
margin: 100px auto;
font-family: Raleway;
padding: 40px;
width: 70%;
min-width: 300px;
}
h1 {
text-align: center;
}
input {
padding: 10px;
width: 100%;
font-size: 17px;
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
border: 1px solid #aaaaaa;
}
/* 유효성 검사에서 오류가 발생한 입력창 표시 */
input.invalid {
background-color: #ffdddd;
}
/* 기본적으로 모든 단계 숨기기 */
.page {
display: none;
}
button {
background-color: #4CAF50;
color: #ffffff;
border: none;
padding: 10px 20px;
font-size: 17px;
font-family: Raleway;
cursor: pointer;
}
button:hover {
opacity: 0.8;
}
#prevBtn {
background-color: #bbbbbb;
}
.step {
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbbbbb;
border: none;
border-radius: 50%;
display: inline-block;
opacity: 0.5;
}
.step.active {
opacity: 1;
}
.step.finish {
background-color: #4CAF50;
}
</style>
<body>
<form id="regForm">
<h1>회원가입:</h1>
<div class="page">이름:
<p><input placeholder="이름:" oninput="this.className = ''" name="fname"></p>
<p><input placeholder="성:" oninput="this.className = ''" name="lname"></p>
</div>
<div class="page">연락처 정보:
<p><input placeholder="이메일:" oninput="this.className = ''" name="email"></p>
<p><input placeholder="전화번호:" oninput="this.className = ''" name="phone"></p>
</div>
<div class="page">생년월일:
<p><input placeholder="일:" oninput="this.className = ''" name="dd"></p>
<p><input placeholder="월:" oninput="this.className = ''" name="nn"></p>
<p><input placeholder="연도:" oninput="this.className = ''" name="yyyy"></p>
</div>
<div style="overflow:auto;">
<div style="float:right;">
<button type="button" id="prevBtn" onclick="nextPrev(-1)">이전</button>
<button type="button" id="nextBtn" onclick="nextPrev(1)">다음</button>
</div>
</div>
<div style="text-align:center;margin-top:40px;">
<span class="step"></span>
<span class="step"></span>
<span class="step"></span>
</div>
</form>
<script>
var currentTab = 0;
showTab(currentTab);
function showTab(stepNo) {
var pageEle = document.getElementsByClassName("page");
pageEle[stepNo].style.display = "block";
if (stepNo == 0) {
document.getElementById("prevBtn").style.display = "none";
}
else {
document.getElementById("prevBtn").style.display = "inline";
}
if (stepNo == (pageEle.length - 1)) {
document.getElementById("nextBtn").innerHTML = "제출";
}
else {
document.getElementById("nextBtn").innerHTML = "다음";
}
changeStepIndicator(stepNo)
}
function nextPrev(stepNo) {
var pageEle = document.getElementsByClassName("page");
if (stepNo == 1 && !checkForm()) return false;
pageEle[currentTab].style.display = "none";
currentTab = currentTab + stepNo;
if (currentTab >= pageEle.length) {
document.getElementById("regForm").submit();
return false;
}
showTab(currentTab);
}
function checkForm() {
var pageEle, inputEle, i, valid = true;
pageEle = document.getElementsByClassName("page");
inputEle = pageEle[currentTab].getElementsByTagName("input");
for (i = 0; i < inputEle.length; i++) {
if (inputEle[i].value == "") {
inputEle[i].className += " invalid";
valid = false;
}
}
if (valid) {
document.getElementsByClassName("step")[currentTab].className += " finish";
}
return valid;
}
function changeStepIndicator(stepNo) {
var i, pageEle = document.getElementsByClassName("step");
for (i = 0; i < pageEle.length; i++) {
pageEle[i].className = pageEle[i].className.replace(" active", "");
}
pageEle[stepNo].className += " active";
}
</script>
</body>
</html>핵심 함수 설명
1. showTab() — 단계 표시 함수
현재 단계 번호(currentTab)에 해당하는 페이지만 화면에 보여줍니다. 첫 번째 단계에서는 [이전] 버튼을 숨기고, 마지막 단계에서는 [다음] 버튼의 텍스트를 [제출]로 변경합니다.
2. nextPrev() — 단계 전환 함수
[다음] 버튼 클릭 시 먼저 checkForm()으로 유효성 검사를 수행하고, 통과하지 못하면 단계가 넘어가지 않습니다. 마지막 단계에서 [제출]을 누르면 form.submit()으로 폼 데이터를 서버에 전송합니다.
3. checkForm() — 유효성 검사 함수
현재 단계의 모든 input 요소를 순회하며 빈 값이 있으면 해당 입력창에 invalid 클래스를 추가해 붉은 배경으로 표시합니다. 모든 값이 유효하면 해당 단계의 인디케이터에 finish 클래스를 추가해 초록색으로 완료 표시를 합니다.
4. changeStepIndicator() — 진행 표시기 업데이트
하단의 원형 인디케이터 중 현재 활성화된 단계에 active 클래스를 적용하여 사용자가 몇 번째 단계에 있는지 직관적으로 알 수 있게 합니다.
실행 결과
위 코드를 실행하면 아래와 같이 이름 → 연락처 정보 → 생년월일 순으로 진행되는 3단계 등록 폼이 나타납니다.

마무리
이 예제는 외부 라이브러리 없이 HTML, CSS, JavaScript만으로 다단계 폼을 구현하는 기본적인 패턴입니다. 실무에서는 정규식 기반의 이메일·전화번호 유효성 검사, 진행률 바 애니메이션, 키보드 Enter 키 지원 등을 추가하면 더욱 완성도 높은 폼을 만들 수 있습니다.