폼은 사용자가 사이트에 말을 거는 창구입니다. 입력칸 하나가 헷갈리면 가입도 결제도 그 자리에서 멈추기 때문에, 저는 폼만큼은 코드로 촘촘히 다듬는 편입니다. 아래 예제는 라벨부터 오류 표시까지 실제 CSS로 하나씩 짚어 봅니다.
12.1 라벨과 입력칸 묶기
라벨을 placeholder로 대신하면 입력을 시작하는 순간 무엇을 적는 칸이었는지 사라집니다. 라벨은 항상 눈에 남아 있어야 합니다.
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.field label {
font-size: 14px;
font-weight: 600;
color: #374151;
}
라벨을 입력칸 위에 세로로 두면 모바일에서도 줄이 깨지지 않습니다. gap 6px가 라벨과 칸을 한 덩어리로 묶어 줍니다.
12.2 입력칸 기본 스타일
브라우저 기본 입력칸은 여백이 빠듯해서 눌러야 할 곳인지 애매합니다. 여백과 테두리, 라운드를 손봐 줍니다.
.field input {
padding: 10px 12px;
font-size: 16px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: #fff;
color: #111827;
}
글자 크기를 16px로 둔 데는 이유가 있습니다. iOS는 16px 미만 입력칸에 포커스가 가면 화면을 확대해 버려서, 이 값 하나로 그 덜컹임을 막습니다.
12.3 포커스 상태 보여 주기
지금 어느 칸에 글을 쓰고 있는지 화면이 알려 줘야 합니다. 기본 outline을 지우기만 하고 대체 표시를 안 넣으면 키보드 사용자가 길을 잃습니다.
.field input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, .15);
}
테두리가 파랗게 변하고 옅은 후광이 칸을 감쌉니다. 지금 여기라고 화면이 손을 들어 주는 셈입니다.
12.4 필수 표시와 도움말
어떤 칸이 필수인지, 무엇을 어떻게 적어야 하는지는 미리 알려 주는 편이 친절합니다. 별표와 짧은 안내 문구를 함께 답니다.
<label>이메일 <span class="req">*</span></label>
<input type="email">
<small class="hint">로그인에 쓰는 주소를 적어 주세요</small>
.req { color: #dc2626; }
.hint {
font-size: 12px;
color: #6b7280;
}
빨간 별표가 필수임을 짚고 회색 안내가 조용히 거듭니다. 사용자는 칸을 채우기 전에 무엇을 넣을지 압니다.
12.5 오류 상태 표시
틀린 입력은 어느 칸이 왜 틀렸는지 그 칸 바로 옆에서 말해 줘야 합니다. 폼 맨 위에 오류를 몰아 두면 사용자가 다시 위로 스크롤해 찾아야 합니다.
.field.error input {
border-color: #dc2626;
background: #fef2f2;
}
.field.error .msg {
color: #dc2626;
font-size: 12px;
}
칸의 테두리가 붉어지고 아래에 이유가 붙습니다. 색만으로 끝내지 않고 문구를 함께 둔 이유는, 색을 구분하기 어려운 사용자도 있기 때문입니다.
12.6 비활성과 읽기 전용
아직 못 누르는 버튼과 손댈 수 없는 칸은 겉모습부터 달라야 합니다. 멀쩡해 보이는데 눌리지 않으면 고장으로 오해받습니다.
.field input:disabled {
background: #f3f4f6;
color: #9ca3af;
cursor: not-allowed;
}
.submit:disabled {
opacity: .6;
cursor: not-allowed;
}
흐린 배경과 금지 커서가 지금은 손댈 수 없다고 미리 일러 줍니다. 사용자는 헛되이 클릭하다 지치지 않습니다.
12.7 가로 폼과 반응형
이름과 성처럼 짧은 칸은 가로로 나란히 두면 공간이 절약됩니다. 다만 화면이 좁아지면 세로로 풀어 줘야 합니다.
.row {
display: flex;
gap: 12px;
}
.row .field { flex: 1; }
@media (max-width: 480px) {
.row { flex-direction: column; }
}
넓은 화면에서는 두 칸이 어깨를 나란히 하고, 480px보다 좁아지면 세로로 쌓여 폭을 꽉 채웁니다. 좁은 화면에서 칸이 눌려 답답해지는 일이 없습니다.
12.8 제출 버튼 배치
제출과 취소를 나란히 둘 때는 주된 행동이 오른쪽에서 도드라지게 합니다. 두 버튼이 같은 무게로 서 있으면 사용자가 잠깐 멈칫합니다.
.actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 20px;
}
.submit {
background: #2563eb;
color: #fff;
padding: 10px 20px;
border-radius: 8px;
}
파란 제출 버튼이 오른쪽 끝에서 시선을 잡고 취소는 그 왼쪽에 물러서 있습니다. 손이 자연스럽게 다음 행동으로 향합니다.
12.9 선택과 여러 줄 입력
드롭다운과 여러 줄 입력칸도 텍스트 칸과 같은 결로 맞춰야 폼이 한 벌로 보입니다. 높이와 테두리, 라운드를 통일합니다.
.field select,
.field textarea {
padding: 10px 12px;
font-size: 16px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: #fff;
}
.field textarea {
min-height: 96px;
resize: vertical;
}
textarea는 세로로만 크기를 조절하게 두면 사용자가 내용에 맞춰 늘리되 가로로 삐져나가 레이아웃을 깨는 일은 막습니다. select도 같은 테두리를 쓰니 폼 전체가 하나의 세트처럼 읽힙니다.
12.10 체크박스와 라디오
체크박스는 칸과 라벨을 가로로 나란히 두고, 라벨까지 클릭 영역으로 잡아 줘야 손끝이 편합니다. 작은 사각형만 노리게 하면 자꾸 빗나갑니다.
.check {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.check input {
width: 18px;
height: 18px;
accent-color: #2563eb;
}
label 태그로 감싸면 글자를 눌러도 체크가 토글됩니다. accent-color 한 줄로 브라우저 기본 체크 색을 브랜드 파랑에 맞추니, 커스텀 이미지를 그리지 않고도 통일감이 납니다.
12.11 조각을 하나로
지금까지의 규칙을 한 폼에 얹으면 이렇게 됩니다. 마크업과 스타일이 서로를 거들어 완결된 입력 화면이 됩니다.
<form class="signup">
<div class="field">
<label for="em">이메일 <span class="req">*</span></label>
<input id="em" type="email">
</div>
<div class="actions">
<button type="button">취소</button>
<button class="submit">가입</button>
</div>
</form>
label의 for가 input의 id와 맺어져 있어 라벨을 눌러도 칸에 커서가 갑니다. 이 작은 연결이 마우스로도 스크린 리더로도 같은 편의를 줍니다.
12.12 정리
좋은 폼은 사용자가 고민할 틈을 주지 않습니다. 라벨을 남겨 두고, 포커스를 또렷이 보이고, 오류를 그 칸 옆에서 설명하고, 못 쓰는 칸은 못 쓰게 보이는 이 네 가지만 지켜도 완주율이 오릅니다. 폼은 결국 사용자와 나누는 짧은 대화이고, 그 대화가 매끄러울수록 끝까지 남습니다.