흐름 설계라고 하면 화살표가 그려진 다이어그램을 떠올리기 쉽지만, 화면에서 흐름은 결국 다단계 폼의 단계 UI로 나타납니다. 지금 몇 번째 단계인지, 다음으로 갈 수 있는지, 뒤로 돌아갈 수 있는지를 코드로 정하는 일입니다. 회원가입 위저드를 예로 단계 UI를 하나씩 짜 보겠습니다.

19.1 단계 인디케이터

사용자는 지금 어디쯤 왔고 얼마나 남았는지 알아야 안심하고 진행합니다. 단계를 원으로 늘어놓고 지나온 단계를 칠해 줍니다.


.steps {
display: flex;
gap: 8px;
}
.step {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #e5e7eb;
color: #6b7280;
font-size: 14px;
}
.step.done {
background: #2563eb;
color: #fff;
}


지나온 단계에 done 클래스가 붙으면 회색 원이 파랗게 채워집니다. 세 개 중 두 개가 파랗다면 한 단계만 남았다는 뜻이니, 사용자는 끝이 가깝다는 것을 눈으로 셉니다.

19.2 진행 바로 남은 길 보이기

원 대신 가로 막대로 진행률을 보여 줄 수도 있습니다. 채워진 길이가 곧 얼마나 왔는지를 말해 줍니다.


.progress {
height: 6px;
background: #e5e7eb;
border-radius: 3px;
overflow: hidden;
}
.progress .bar {
height: 100%;
width: 66%;
background: #2563eb;
transition: width .3s ease;
}


안쪽 막대의 폭을 66%로 두면 세 단계 중 둘째에 와 있음이 드러납니다. transition 덕분에 다음 단계로 넘어갈 때 막대가 스르르 차오르며 진전을 느끼게 합니다.

19.3 유효하기 전엔 다음 버튼 잠그기

필수 값이 비어 있는데 다음으로 넘기면 뒤늦게 되돌아오는 헛걸음이 생깁니다. 조건을 채우기 전까지 다음 버튼을 눌리지 않게 둡니다.


.next-btn:disabled {
background: #93c5fd;
cursor: not-allowed;
}
.next-btn {
background: #2563eb;
color: #fff;
padding: 10px 18px;
border: none;
border-radius: 6px;
}


입력이 유효해지면 스크립트가 disabled 속성을 떼고, 그 순간 버튼이 또렷한 파랑으로 살아납니다. 잠긴 동안엔 흐린 색과 금지 커서가 아직 덜 됐다고 말해 줍니다.

19.4 CSS만으로 단계 전환하기

자바스크립트 없이도 라디오 버튼과 :checked 만으로 단계를 바꿀 수 있습니다. 숨은 라디오가 어느 것이 선택됐는지 기억하고, 그에 맞는 화면만 보입니다.


<input type="radio" name="step" id="s1" checked>
<input type="radio" name="step" id="s2">

.panel { display: none; }
#s1:checked ~ .panels .p1 { display: block; }
#s2:checked ~ .panels .p2 { display: block; }


선택된 라디오가 바뀔 때마다 형제 결합자 ~ 가 짝이 맞는 패널만 켭니다. 상태를 마크업이 직접 들고 있으니, 스크립트가 꺼져 있어도 단계 이동이 끊기지 않습니다.

19.5 라벨을 버튼처럼 쓰기

라디오 자체는 숨기고, 그와 연결된 라벨을 다음 버튼으로 꾸미면 사용자는 평범한 버튼을 누른다고 느낍니다.


input[name="step"] {
position: absolute;
opacity: 0;
pointer-events: none;
}
.nav-label {
display: inline-block;
background: #2563eb;
color: #fff;
padding: 10px 18px;
border-radius: 6px;
cursor: pointer;
}


라디오는 화면 밖으로 숨었지만 여전히 상태를 쥐고 있고, 파란 라벨을 누르면 for 로 연결된 라디오가 켜집니다. 겉은 버튼, 속은 상태 저장 장치인 셈입니다.

19.6 뒤로 가기 열어 두기

앞으로만 가는 폼은 오타 하나를 못 고쳐 사용자를 가둡니다. 이전 단계로 돌아가는 라벨을 나란히 두어 되돌릴 길을 줍니다.


.nav {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.nav-back {
background: #f3f4f6;
color: #4b5563;
padding: 10px 18px;
border-radius: 6px;
cursor: pointer;
}


다음 버튼은 오른쪽, 뒤로 버튼은 왼쪽에 두어 방향과 위치를 맞춥니다. 뒤로 버튼은 조용한 회색으로 물러서서, 주된 동작인 다음 버튼의 자리를 침범하지 않습니다.

19.7 완료 상태 보여 주기

마지막 단계까지 마치면 지금까지의 긴장을 풀어 주는 화면이 필요합니다. 모든 단계 원을 채우고 확인 표시를 띄웁니다.


.step.done::after {
content: "\2713";
}
.done-panel {
text-align: center;
padding: 32px;
}
.done-panel .check {
width: 56px;
height: 56px;
border-radius: 50%;
background: #16a34a;
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 28px;
}


완료된 단계마다 체크 표시가 들어오고, 마지막엔 초록 원 안의 큰 확인 표시가 끝났음을 분명히 알립니다. 여기서 \2713 은 체크 문자를 CSS로 넣는 이스케이프 표기입니다.

19.8 주소와 단계를 잇기

단계마다 고유 주소를 주면 새로고침을 해도 그 자리에 머물고, 링크로 특정 단계를 바로 열 수도 있습니다. :target 이 주소의 해시와 맞는 패널만 보여 줍니다.


.panel {
display: none;
}
.panel:target {
display: block;
}
<a href="#step2" class="nav-label">다음</a>
<section id="step2" class="panel"> ... </section>


주소가 #step2 로 바뀌면 그 id를 가진 패널만 켜집니다. 뒤로 가기 버튼도 브라우저의 기본 뒤로 가기와 자연스레 맞물려, 사용자가 이미 아는 방식으로 단계를 오갑니다.

19.9 정리

사용자 흐름은 추상적인 설계도가 아니라 단계 인디케이터, 잠긴 다음 버튼, 상태를 쥔 라디오, 되돌아갈 뒤로 버튼, 안도감을 주는 완료 화면으로 손에 잡힙니다. 지금 어디인지 보여 주고, 준비됐을 때만 앞으로 보내고, 언제든 뒤로 열어 두면, 긴 폼도 사용자가 길을 잃지 않고 끝까지 따라옵니다.