폼과 입력
사용자가 덜 틀리고, 틀려도 바로 고칠 수 있게 입력을 받는 법이다.
목차 (13)
레이블 위치와 필수·선택 표시
입력 칸마다 무엇을 묻는지 적은 레이블이 늘 보여야 한다. NN/g는 모바일과 짧은 데스크톱 폼에서는 레이블을 칸 바로 위에, 아주 긴 데스크톱 폼에서는 칸 옆에 두라고 하고, 레이블이 위아래 두 칸에서 같은 거리에 있어 어느 칸의 것인지 모호해지는 배치를 피하라고 한다.
칸 안의 흐린 글자(플레이스홀더)를 레이블 대신 쓰면 안 되는 이유를 NN/g는 이렇게 든다. 글자를 쓰면 사라져 기억에 기대게 하고, 제출 전에 무엇을 썼는지 검토할 수 없고, 오류가 났을 때 무엇을 고쳐야 하는지 모르고, 이미 채워진 기본값으로 오해해 건너뛰기도 하며, 기본 회색은 대비가 낮아 읽기도 어렵다. 칸 위로 올라가는 "떠오르는 레이블"은 레이블을 남기긴 하지만 글자가 작아지고 칸이 붐빈다. 위에 두는 평범한 레이블이 가장 탈이 없다.
NN/g의 필수 칸 글은 필수 칸을 모두 표시하라고 한다. 폼 위에 "모두 필수"라고 한 줄 적는 것만으로는 사람들이 건너뛰고 잊는다. 별표(*)는 널리 알려져 있고 자리를 덜 차지하며, 빨간색이 기대되는 색이지만 옅은 회색처럼 대비가 낮은 색은 피한다. 선택 칸에 "(선택)"을 함께 적으면 어느 칸을 비워도 되는지 추측할 필요가 없어진다. 아이디와 비밀번호뿐인 로그인 폼은 별표를 생략해도 된다. 화면 낭독기가 "필수"라고 읽도록 입력 요소에는 required를 단다.
<label for="email">이메일</label>
<p id="email-hint">로그인 아이디로 씁니다</p>
<input id="email" type="email" autocomplete="email"
aria-describedby="email-hint email-error" required>
<p id="email-error" hidden>이메일 주소 형식으로 입력하세요. 예: minji@example.com</p>입력 형식과 길이 단서
사람은 칸 모양을 보고 무엇을 얼마나 쓸지 짐작한다. NN/g는 칸 폭을 들어갈 값의 길이와 비슷하게 하라고 한다. 쓴 값이 칸 안에 다 보이지 않으면 틀린 것을 알아채기 어렵기 때문이다. 같은 글은 표본의 99.9%가 19자 이하였던 도시 이름 칸을 예로 든다.
- 형식을 미리 말한다: "예: 010-1234-5678"처럼 받는 형식을 레이블 가까이에 둔다. 칸 안 플레이스홀더에 두면 쓰는 순간 사라진다.
- 형식을 강요하지 않는다: 하이픈이나 공백이 섞여 들어와도 받아서 프로그램이 정리한다. 사람에게 기계의 형식을 맞추게 하지 않는다.
- 고를 것이 두세 개면 라디오 버튼: NN/g는 선택지가 두세 개뿐이면 드롭다운 대신 라디오 버튼을 쓰라고 한다. 한눈에 다 보이고 한 번에 고른다.
검증 시점: 입력 중, 포커스 아웃, 제출
언제 "틀렸다"고 알릴지는 폼의 인상을 크게 바꾼다. 글자마다 검사하면 이메일의 첫 글자를 치자마자 "형식이 아닙니다"가 뜬다. 아직 다 쓰지도 않았는데 혼나는 셈이다. 제출할 때만 검사하면 긴 폼을 다 채운 뒤에야 위로 돌아가 여러 칸을 고쳐야 한다.
1. 첫 글자 m → 입력 중: 오류, 포커스 아웃: 표시 없음, 제출 때: 표시 없음.
NN/g는 검증을 가능하면 칸 옆에서(인라인) 하되, 대부분의 경우 사용자가 그 칸을 끝내고 다음 칸으로 넘어갈 때까지 오류를 보이지 말라고 한다. Baymard의 결론도 같다. 너무 이른 검증은 경험을 해치므로 칸을 떠날 때나 우편번호·카드 번호처럼 길이가 정해진 값이 다 찼을 때 검사하고, 한번 뜬 오류는 사용자가 고쳐서 맞는 값이 되는 순간 글자 단위로 지운다. 맞게 쓴 칸에 체크 표시를 주는 긍정 검증도 권한다. 다만 NN/g는 성공 표시를 모든 칸에 남발하면 어수선해진다고 덧붙인다. 비밀번호 강도처럼 맞추기 어려운 칸에 주로 쓴다.
| 시점 | 좋은 점 | 나쁜 점 | 맞는 곳 |
|---|---|---|---|
| 입력 중(글자마다) | 즉시 안다 | 다 쓰기 전부터 오류 | 비밀번호 조건 목록처럼 맞춰 가는 칸, 이미 뜬 오류를 지울 때 |
| 포커스 아웃 | 다 쓴 뒤 바로 안다 | 칸을 건너뛰기만 해도 오류가 뜰 수 있다 | 대부분의 칸(빈 칸은 제출 때 알린다) |
| 제출 | 쓰는 동안 방해가 없다 | 한꺼번에 몰려 늦게 안다 | 짧은 폼, 서버만 알 수 있는 오류(이미 쓰는 아이디 등) |
어느 방식이든 서버에서 다시 검사한다. 화면의 검증은 사용자를 돕는 것이지 막는 장치가 아니다. 요청은 누구나 직접 보낼 수 있다.
오류 메시지 쓰는 법
오류 메시지는 무엇이 틀렸고 어떻게 고치는지 말해야 한다. WCAG 3.3.1(오류 식별, A)은 자동으로 찾은 입력 오류가 있으면 어느 항목이 틀렸는지 밝히고 오류를 글로 설명하라고 요구한다(W3C).
- 칸 바로 옆에: NN/g는 오류를 해당 칸 옆에 두고, 빨간색에 아이콘을 더해 색을 구분하기 어려운 사람도 찾게 하라고 한다. 아이콘 뒤에 숨겨 마우스를 올려야 보이는 툴팁은 놓치기 쉽다.
- 요약은 보조로: 맨 위의 오류 요약은 전체를 보여 주지만 그것만으로는 부족하다. GOV.UK는 오류가 하나여도 요약을 두고, 요약의 항목마다 해당 칸으로 가는 링크를 달고, 키보드 포커스를 요약으로 옮기며, 각 칸에도 메시지를 함께 보이라고 한다.
- 문장은 고칠 방법으로: "잘못된 입력입니다" 대신 "휴대전화 번호가 한 자리 모자랍니다"처럼 무엇이 문제인지, "11자리 숫자로 입력하세요"처럼 무엇을 하면 되는지 쓴다. 사용자를 탓하는 말투("잘못 입력하셨습니다")는 피한다.
- 입력은 남긴다: 오류가 났다고 칸을 비우면 처음부터 다시 써야 한다. 비밀번호처럼 보안상 지워야 하는 칸이 아니면 그대로 둔다.
- 반복되면 설계를 의심한다: NN/g는 한 번의 시도에서 같은 오류가 세 번 이상 나오면 사람이 아니라 설계에 문제가 있다는 신호라고 한다.
칸과 메시지는 aria-describedby로 이어 두면 화면 낭독기가 칸에 들어갈 때 안내와 오류를 함께 읽는다(위 레이블 절의 코드). 폼 밖의 오류 화면과 복구는 인터랙션과 상태 노트에 있다.
한 열 레이아웃
NN/g는 칸마다 한 줄씩, 한 열로 쌓으라고 한다. 여러 열은 아래로 내려가던 흐름을 끊는다. 두 열 폼에서는 오른쪽 칸을 건너뛰거나, 다음 칸이 오른쪽인지 아래인지 헷갈린다. 같은 글은 사용성 지침을 따른 폼이 첫 시도에 제출된 비율이 78%로, 따르지 않은 폼의 42%보다 높았다는 2014년 연구(Seckler 외)를 인용한다.
예외는 시·구·우편번호처럼 짧고 논리적으로 붙어 있는 칸이다. 이런 칸은 한 줄에 둘 수 있다. 주제가 두 가지인 폼(배송지와 결제)은 칸을 두 묶음으로 나누고, 묶음에 제목을 달아 화면 낭독기에도 묶음이 드러나게 한다(fieldset과 legend).
단계형 폼과 진행 표시
칸이 많으면 단계를 나눈다. GOV.UK의 질문 페이지 패턴은 한 페이지에 질문 하나를 두면 무엇을 묻는지 이해하기 쉽다고 하고, 서로 관련된 것을 함께 물어야 하면 질문 대신 설명형 제목을 쓰라고 한다. 페이지마다 위에 "이전" 링크를 두되 브라우저의 뒤로 가기도 깨지 않게 하고, 버튼은 "다음"이 아니라 "계속"으로 적는다.
4단계 중 1단계: 배송지. 한 화면에는 서로 관련된 질문 한 묶음만 둔다.
- 진행 표시는 시험해 보고 더한다: GOV.UK는 먼저 진행 표시 없이 시험해 보고, 질문의 순서·종류·수를 다듬는 것이 먼저라고 한다. 전체 질문 수는 믿을 수 있을 때만 보이고, 모든 단계를 한 줄에 늘어놓는 표시는 작은 화면에서 잘 안 보이고 자리를 차지한다고 경고한다.
- 뒤로 가도 남는다: 이전 단계로 돌아갔다 와도 입력이 사라지지 않아야 한다. 이미 한 번 입력한 정보를 다시 묻지 않는 것은 WCAG 2.2의 3.3.7(중복 입력, A)이기도 하다.
- 마지막은 확인 화면: 고른 것을 모아 보여 주고 줄마다 "변경"으로 그 단계에 돌아가게 한다. 결제나 신청처럼 되돌리기 어려운 제출 직전에 특히 필요하다.
자동 완성과 모바일 키보드
가장 좋은 입력은 입력하지 않는 것이다. 브라우저와 비밀번호 관리자는 저장해 둔 이름·주소·카드를 채워 줄 수 있는데, 어느 칸이 무엇인지 알려 주는 것이 autocomplete 속성이다. MDN에 토큰 목록이 있다. autocomplete="off"를 달아도 대부분의 브라우저는 로그인 칸의 비밀번호 관리자를 막지 않는다. WCAG 1.3.5(입력 목적 식별, AA)는 사용자 자신에 대한 정보를 받는 칸의 목적을 프로그램이 알 수 있게 하라고 요구하는데, HTML에서 그 충분한 방법이 바로 이 속성이다(W3C).
| 칸 | 속성 |
|---|---|
| 이름 | type="text" autocomplete="name" |
| 이메일 | type="email" autocomplete="email" |
| 휴대전화 | type="tel" autocomplete="tel" |
| 우편번호 | type="text" inputmode="numeric" autocomplete="postal-code" |
| 인증번호 | type="text" inputmode="numeric" autocomplete="one-time-code" |
| 새 비밀번호 | type="password" autocomplete="new-password" |
| 현재 비밀번호 | type="password" autocomplete="current-password" |
휴대폰에서는 칸에 맞는 가상 키보드를 띄우는 것이 중요하다. type="email"은 골뱅이가 있는 키보드와 기본 형식 검사를, type="tel"은 전화 숫자판을 준다. 인증번호·우편번호처럼 숫자로만 된 문자열은 type="number" 대신 inputmode="numeric"을 쓴다. MDN의 설명대로 inputmode는 키보드만 바꾸고 값을 검사하지는 않는다. 앞자리 0이 있는 우편번호를 숫자 칸에 넣으면 0이 사라지거나 화살표 버튼이 생기는 문제를 피할 수 있다.
비밀번호 필드: 보기 토글과 붙여넣기
비밀번호 칸은 가려져 있어 오타를 알아채기 어렵다. 그래서 흔히 "비밀번호 확인" 칸을 하나 더 두지만, web.dev는 브라우저가 강한 비밀번호를 제안하는 지금은 두 번 묻는 것이 의미가 없다고 하고, 대신 입력한 글자를 확인할 수 있는 비밀번호 보기 토글을 두라고 한다. 가입 칸에는 autocomplete="new-password", 로그인 칸에는 autocomplete="current-password"를 달아 비밀번호 관리자가 알맞게 제안하고 채우게 한다.
붙여넣기와 자동 채우기를 막지 않는다. 보안 › 인증과 인가 노트에 정리한 미국 표준 기술 연구소의 디지털 신원 지침(NIST SP 800-63B)도 비밀번호 관리자를 쓰면 더 강한 비밀번호를 고르게 된다는 이유로 이를 막지 말라고 하고, 문자 종류를 섞으라는 조합 규칙 대신 길이와 유출 목록 확인을 요구한다. 서버 쪽 저장도 그 노트에 있다. 조건을 안내해야 한다면 쓰기 전에 보이고, 쓰는 동안 하나씩 체크되는 목록이 좋다(입력 중 검증이 맞는 드문 경우다).
날짜 입력
날짜는 쓰임에 따라 입력 방식이 다르다. GOV.UK의 날짜 패턴은 생년월일처럼 외우고 있는 날짜는 일·월·년 세 칸에 숫자로 받으라고 한다. 달력 선택기는 가까운 미래나 최근 과거의 날짜를 고를 때, 날짜와 함께 요일이나 몇째 주인지를 알아야 할 때, 다른 날짜와 견줘 봐야 할 때만 쓰고, 자바스크립트에 기대는 달력만 두지 말고 글자로 입력할 칸을 함께 두라고 한다.
- 순서는 지역에 맞게: GOV.UK는 영국식 일·월·년 순서다. 한국어 서비스는 년·월·일로 두고, 각 칸 위에 "년", "월", "일"을 레이블로 단다.
- 형식 예시: "예: 1998 3 27"처럼 앞자리 0을 붙이지 않아도 된다는 것까지 보인다.
- 브라우저 기본 날짜 칸:
<input type="date">는 기기에 맞는 선택기를 공짜로 주지만 모양과 형식을 브라우저가 정한다. 생년월일처럼 먼 날짜에는 연도를 한참 넘겨야 해서 불편할 수 있다.
한국 주소와 전화번호
한국 주소는 2014년 1월 1일부터 도로명과 건물번호로 위치를 나타내는 도로명주소를 본격적으로 쓰고, 표기는 시·도, 시·군·구, 도로명과 건물번호, 상세주소(동·층·호), 괄호 안 참고항목 순이다(위키백과). 우편번호는 2015년 8월 1일부터 국가기초구역번호 다섯 자리를 쓴다. 그 전의 여섯 자리(예: 122-090) 우편번호는 이때 폐지됐다(위키백과).
- 주소 검색: 국내 서비스는 대개 주소 검색 창으로 우편번호와 도로명주소를 한 번에 채우고, 그 뒤 포커스를 상세주소 칸으로 옮긴다. 사람이 우편번호를 외워 쓰는 일은 드물다. 검색이 실패하거나 해외 주소를 받아야 할 때를 위해 직접 입력하는 길도 남긴다.
- 칸 나누기: 기본 주소와 상세주소를 나누면 상세주소 빠뜨림이 줄어든다. 자동 채우기에는
postal-code,address-line1,address-line2를 단다. - 전화번호: 휴대전화는 010으로 시작하고 서울 지역번호는 02다(위키백과). 칸은 하나로 두고
type="tel",autocomplete="tel"을 단다. 앞자리·가운데·끝을 세 칸으로 나누면 붙여넣기와 자동 채우기가 깨진다. 하이픈·공백을 섞어 써도 받아서 숫자만 남기고, 보여 줄 때 형식을 맞춘다. - 이름: 한국 이름은 성이 먼저라 "성"과 "이름" 두 칸 순서가 서양 폼과 반대다. 한 칸(
autocomplete="name")으로 받는 편이 단순하다.
버튼 문구와 이중 제출 방지
제출 버튼의 문구는 누르면 일어날 일을 말한다. web.dev는 "제출" 대신 "로그인"·"계정 만들기"를 쓰라고 하고, GOV.UK는 정보를 저장한다면 "저장하고 계속", 저장하지 않는다면 그냥 "계속"처럼 실제 동작에 맞는 동사를 고르라고 한다. 결제 버튼이라면 "결제하기"보다 "29,000원 결제하기"가 더 분명하다.
느린 연결에서 반응이 없으면 사람은 버튼을 다시 누른다. 결제·주문 폼에서는 이것이 중복 결제가 된다. web.dev는 로그인 버튼을 한 번 누른 뒤 비활성으로 바꾸는 것을 고려하라고 하고, GOV.UK는 두 번 누르기를 막는 기능을 두되 자바스크립트를 켠 사용자에게만 통하므로 서버에서도 막고, 먼저 진행 상황을 보여 주라고 한다. 서버 쪽 방법은 같은 주문에 같은 키를 붙여 보내는 멱등 키다(API 설계 노트의 멱등 키와 재시도).
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (button.getAttribute('aria-disabled') === 'true') return;
button.setAttribute('aria-disabled', 'true');
button.textContent = '결제하는 중…';
try {
// 같은 주문에는 같은 키: 요청이 두 번 가도 서버가 한 번만 처리한다
await pay(order, { idempotencyKey: order.id });
} finally {
button.removeAttribute('aria-disabled');
button.textContent = '결제하기';
}
});위 코드는 disabled 대신 aria-disabled를 쓴다. disabled 버튼은 포커스를 잃어 키보드 사용자의 위치가 사라지고, 화면 낭독기에서 버튼이 갑자기 없어진 것처럼 느껴질 수 있기 때문이다. 그 대신 스크립트에서 두 번째 제출을 직접 무시한다.
흔한 오해
- "플레이스홀더로 레이블을 대신하면 깔끔하다" — 쓰는 순간 질문이 사라진다. 레이블은 칸 위에 늘 보이게 둔다.
- "입력하는 즉시 검증할수록 친절하다" — 다 쓰기 전의 오류는 방해다. 칸을 떠날 때 알리고, 고치는 즉시 지운다.
- "두 열로 놓으면 폼이 짧아 보여 좋다" — 흐름이 끊기고 칸을 빠뜨린다. 짧고 붙어 있는 칸만 한 줄에 둔다.
- "숫자를 받으니 type=number" — 인증번호·우편번호·카드 번호는 셈하는 수가 아니다.
inputmode="numeric"을 쓴다. - "비밀번호 붙여넣기를 막으면 안전하다" — 비밀번호 관리자를 막아 약한 비밀번호를 부른다. 보기 토글과 붙여넣기를 허용한다.
- "autocomplete=off로 자동 채우기를 끈다" — 로그인 칸에서는 대부분의 브라우저가 무시하고, 사용자의 수고만 늘린다. 맞는 토큰을 단다.
- "버튼을 막았으니 중복 결제는 없다" — 화면의 방지는 자바스크립트가 돌 때만 통한다. 서버에서 멱등 키로 한 번 더 막는다.
핵심 자료
요지는 직접 정리한 한 줄이다.
| 자료 | 한 줄 요지 |
|---|---|
| NN/g — Reporting Errors in Forms | 오류는 칸 옆에, 색과 아이콘으로, 칸을 떠난 뒤에. 요약만으로는 부족하다. |
| NN/g — Placeholders Are Harmful | 플레이스홀더를 레이블로 쓰면 기억·검토·오류 복구가 모두 어려워진다. |
| NN/g — Website Forms Usability | 레이블은 칸 위에 가깝게, 한 열로, 칸 폭은 들어갈 값의 길이로. |
| Baymard — Inline Form Validation | 너무 이른 검증을 피하고, 고친 즉시 오류를 지우고, 맞으면 맞다고 보인다. |
| GOV.UK Design System | 한 페이지에 한 가지, 날짜 세 칸, 오류 요약, 결과를 말하는 버튼. 공공 서비스에서 검증된 패턴 모음. |
| MDN — autocomplete | 자동 채우기 토큰 목록과, off가 비밀번호 관리자를 막지 못한다는 점. |
| web.dev — Sign-in form best practices | 레이블, 맞는 type, 보기 토글, current/new-password, 한 번 누른 뒤 버튼 막기. |
- 10 Design Guidelines for Reporting Errors in Forms — NN/g
- Placeholders in Form Fields Are Harmful — NN/g
- Marking Required Fields in Forms — NN/g
- Website Forms Usability: Top 10 Recommendations — NN/g
- Usability Testing of Inline Form Validation — Baymard Institute
- Error summary — GOV.UK Design System
- Question pages — GOV.UK Design System
- Dates — GOV.UK Design System
- HTML attribute: autocomplete — MDN Web Docs
- inputmode — MDN Web Docs
- Sign-in form best practices — web.dev
- Understanding SC 1.3.5 Identify Input Purpose — W3C
- Understanding SC 3.3.1 Error Identification — W3C
- 대한민국의 우편번호 — 위키백과
- 도로명주소 — 위키백과
- 대한민국의 전화번호 — 위키백과