폼은 사용자가 가장 자주 마주치면서도 가장 자주 지치는 자리라, 여기가 매끄러우면 가입도 결제도 술술 넘어갑니다. 라벨 연결부터 상태 표시, 인라인 검증, 플로팅 라벨까지 실제 코드로 다듬어 보겠습니다. 폼은 사용자에게 무언가를 청하는 대화입니다.
23.1 라벨과 입력 연결
이름표를 칸 안에 흐릿하게 넣으면 적기 시작하는 순간 사라져 버립니다. label을 밖에 두고 for와 id로 묶으면 늘 보이고, 라벨을 눌러도 칸에 포커스가 갑니다.
<label for="email">이메일</label>
<input type="email" id="email" name="email">
label {
display: block;
margin-bottom: 6px;
font-size: 14px;
color: #374151;
}
for와 id가 같은 값으로 이어져 있으면 보조기기도 이 칸이 무엇인지 읽어 줍니다. 라벨 클릭 영역이 넓어져 손가락으로도 정확히 포커스를 잡습니다.
23.2 입력 상태 스타일
칸은 평소, 손이 닿은 지금, 못 만지는 상태를 각기 다르게 보여 줘야 합니다. 상태마다 테두리와 배경을 갈라 두면 사용자가 지금 형편을 눈으로 압니다.
input {
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 16px;
}
input:focus {
border-color: #2563eb;
outline: none;
box-shadow: 0 0 0 3px rgba(37,99,235,.15);
}
input:disabled {
background: #f3f4f6;
color: #9ca3af;
}
손이 닿은 칸은 파란 테두리에 옅은 후광이 돌고, 못 만지는 칸은 회색으로 물러섭니다. font-size를 16px로 둔 건 모바일에서 칸을 눌렀을 때 화면이 확대되는 걸 막기 위해서입니다.
23.3 :focus-within으로 필드 강조
라벨과 칸을 한 묶음으로 감싸 두면, 그 안 어디든 포커스가 들어왔을 때 묶음 전체를 강조할 수 있습니다.
<div class="field">
<label for="tel">연락처</label>
<input type="tel" id="tel">
</div>
.field {
padding: 12px;
border-radius: 8px;
transition: background .15s;
}
.field:focus-within {
background: #eff6ff;
}
.field:focus-within label {
color: #2563eb;
}
칸을 누르면 칸만이 아니라 라벨을 포함한 묶음 전체가 옅은 파랑으로 물듭니다. 지금 어느 항목을 적고 있는지가 훨씬 또렷해집니다.
23.4 인라인 검증
다 채우고 버튼을 누른 뒤에야 틀렸다고 하면 사용자는 무더기로 퇴짜를 맞습니다. :valid와 :invalid로 적는 그 자리에서 맞는지 짚어 줍니다.
input:invalid {
border-color: #ef4444;
}
input:valid {
border-color: #22c55e;
}
input:placeholder-shown {
border-color: #d1d5db;
}
비어 있을 때는 붉게 나서지 않도록 placeholder-shown을 함께 걸어 두는 게 요령입니다. 빈 칸을 붙잡고 이미 틀렸다고 다그치지 않고, 무언가 적기 시작한 뒤에만 초록과 빨강으로 답합니다.
23.5 input type과 inputmode
무엇을 적느냐에 맞는 자판을 띄워 주면 모바일에서 손품이 확 줄어듭니다. type과 inputmode를 알맞게 지정합니다.
<input type="email" inputmode="email">
<input type="tel" inputmode="tel">
<input type="number" inputmode="numeric">
<input inputmode="decimal">
tel이면 숫자 자판이, email이면 골뱅이와 점이 놓인 자판이 바로 뜹니다. 사용자가 자판을 뒤져 문자를 바꿀 일이 없어지니, 작은 화면에서 특히 고맙습니다.
23.6 autocomplete로 손품 덜기
사용자가 이미 아는 것을 매번 새로 치게 하는 건 낭비입니다. autocomplete를 붙이면 브라우저가 저장해 둔 값을 곧장 채워 줍니다.
<input name="name" autocomplete="name">
<input type="email" autocomplete="email">
<input autocomplete="street-address">
<input type="password" autocomplete="current-password">
정해진 이름을 정확히 적어 줘야 브라우저가 알아듣고 제안을 띄웁니다. 특히 비밀번호 칸에 current-password와 new-password를 구분해 주면 암호 관리기가 제대로 동작합니다.
23.7 플로팅 라벨
칸이 비면 라벨이 안내문처럼 칸 안에 앉아 있다가, 무언가 적으면 위로 떠올라 이름표가 되게 할 수 있습니다. :placeholder-shown이 열쇠입니다.
<div class="float">
<input id="nick" placeholder=" ">
<label for="nick">닉네임</label>
</div>
placeholder를 공백 한 칸으로 두는 게 요령입니다. 칸이 비면 placeholder-shown이 참이 되어 라벨을 가운데로 내리고, 무언가 적히면 라벨이 위로 올라갑니다.
.float {
position: relative;
}
.float label {
position: absolute;
left: 12px;
top: 12px;
color: #9ca3af;
transition: .15s;
}
.float input:not(:placeholder-shown) + label,
.float input:focus + label {
top: -8px;
font-size: 12px;
color: #2563eb;
}
비었을 때는 라벨이 안내문 자리에 있다가, 적기 시작하면 작아지며 위로 미끄러져 올라갑니다. 이름표가 사라지지 않으면서도 칸을 산뜻하게 비워 둘 수 있습니다.
23.8 fieldset으로 묶기
서로 얽힌 물음은 한자리에 모아 두면 사용자가 대화하듯 답합니다. fieldset과 legend로 묶으면 의미까지 함께 묶입니다.
<fieldset>
<legend>배송지</legend>
<label for="zip">우편번호</label>
<input id="zip" inputmode="numeric">
<label for="addr">주소</label>
<input id="addr" autocomplete="street-address">
</fieldset>
legend가 이 묶음이 무엇에 관한 것인지 제목을 달아 주니, 보조기기도 우편번호와 주소가 배송지 한 덩어리임을 읽어 줍니다. 관련된 것끼리 붙여 두는 것만으로 폼이 대화의 결을 얻습니다.
23.9 정리
좋은 폼은 예의 바른 대화라, 라벨로 또렷이 청하고 상태로 지금 형편을 비추며 틀리면 그 자리에서 다정히 일러 줍니다. focus-within으로 지금 칸을 강조하고, 알맞은 inputmode와 autocomplete로 손품을 덜고, fieldset으로 결을 지어 주면 사용자는 지치지 않고 끝까지 답합니다. 가장 좋은 입력 칸은 애초에 묻지 않아도 되는 칸입니다.