테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

폼과 입력

사용자가 덜 틀리고, 틀려도 바로 고칠 수 있게 입력을 받는 법이다.

목차 (13)
  1. 레이블 위치와 필수·선택 표시
  2. 입력 형식과 길이 단서
  3. 검증 시점: 입력 중, 포커스 아웃, 제출
  4. 오류 메시지 쓰는 법
  5. 한 열 레이아웃
  6. 단계형 폼과 진행 표시
  7. 자동 완성과 모바일 키보드
  8. 비밀번호 필드: 보기 토글과 붙여넣기
  9. 날짜 입력
  10. 한국 주소와 전화번호
  11. 버튼 문구와 이중 제출 방지
  12. 흔한 오해
  13. 핵심 자료

레이블 위치와 필수·선택 표시

입력 칸마다 무엇을 묻는지 적은 레이블이 늘 보여야 한다. NN/g는 모바일과 짧은 데스크톱 폼에서는 레이블을 칸 바로 위에, 아주 긴 데스크톱 폼에서는 칸 옆에 두라고 하고, 레이블이 위아래 두 칸에서 같은 거리에 있어 어느 칸의 것인지 모호해지는 배치를 피하라고 한다.

레이블 위치 세 가지. 위 레이블은 칸 바로 위에 이메일이라고 적혀 있고, 옆 레이블은 칸 왼쪽에 있으며, 플레이스홀더만 쓴 칸은 비었을 때만 이메일이 보이고 입력을 시작하면 사라진다위훑기 쉽고 좁은 화면에 맞다옆아주 긴 데스크톱 폼에서플레이스홀더만입력하면 질문이 사라진다이메일minji@ex휴대전화이메일minji@ex휴대전화비었을 때이메일입력하는 중minji@ex무슨 칸?레이블은 칸에 가깝게, 다른 칸과는 멀게 둔다. 어느 칸의 레이블인지 헷갈리지 않게
레이블 위치 세 가지. 위 레이블이 기본이고, 플레이스홀더만 쓴 칸은 입력을 시작하는 순간 무엇을 묻던 칸인지 사라진다.

칸 안의 흐린 글자(플레이스홀더)를 레이블 대신 쓰면 안 되는 이유를 NN/g는 이렇게 든다. 글자를 쓰면 사라져 기억에 기대게 하고, 제출 전에 무엇을 썼는지 검토할 수 없고, 오류가 났을 때 무엇을 고쳐야 하는지 모르고, 이미 채워진 기본값으로 오해해 건너뛰기도 하며, 기본 회색은 대비가 낮아 읽기도 어렵다. 칸 위로 올라가는 "떠오르는 레이블"은 레이블을 남기긴 하지만 글자가 작아지고 칸이 붐빈다. 위에 두는 평범한 레이블이 가장 탈이 없다.

같은 가입 폼 두 개. 왼쪽은 아무 표시가 없어 회사 칸을 비워도 되는지 알 수 없고, 오른쪽은 이름과 이메일에 빨간 별표, 회사에 선택이라고 적혀 있으며 맨 위에 별표의 뜻이 있다표시 없음이름이메일회사필수 * · 선택 표시* 표시는 꼭 입력이름 *이메일 *회사 (선택)별표는 색만으로 구분하지 않게 기호로 두고, 입력 요소에는 required(또는 aria-required)를 단다
같은 폼에 필수·선택 표시를 한 것과 하지 않은 것. 별표의 뜻은 폼 위에 한 번 적고, 별표는 칸마다 단다.

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자 이하였던 도시 이름 칸을 예로 든다.

왼쪽은 우편번호, 휴대전화, 상세주소 칸이 모두 같은 폭이고 형식 안내가 없다. 오른쪽은 우편번호 칸이 짧고 상세주소 칸이 길며, 레이블 아래에 숫자 5자리, 예시 번호 같은 형식 단서가 있다같은 폭, 단서 없음값의 길이에 맞춘 폭 + 형식 단서우편번호우편번호숫자 5자리06236휴대전화휴대전화예: 010-1234-5678010-1234-5678상세주소상세주소동·층·호101동 1203호칸 폭이 곧 "얼마나 쓰면 되는지"의 단서다. 예시는 레이블 옆에 늘 보이게
같은 세 칸을 같은 폭으로 둔 폼과 값의 길이에 맞춘 폼. 오른쪽은 우편번호가 짧은 칸이라 5자리라는 것이, 형식 예시는 레이블 옆에 늘 보인다.
  • 형식을 미리 말한다: "예: 010-1234-5678"처럼 받는 형식을 레이블 가까이에 둔다. 칸 안 플레이스홀더에 두면 쓰는 순간 사라진다.
  • 형식을 강요하지 않는다: 하이픈이나 공백이 섞여 들어와도 받아서 프로그램이 정리한다. 사람에게 기계의 형식을 맞추게 하지 않는다.
  • 고를 것이 두세 개면 라디오 버튼: NN/g는 선택지가 두세 개뿐이면 드롭다운 대신 라디오 버튼을 쓰라고 한다. 한눈에 다 보이고 한 번에 고른다.

검증 시점: 입력 중, 포커스 아웃, 제출

언제 "틀렸다"고 알릴지는 폼의 인상을 크게 바꾼다. 글자마다 검사하면 이메일의 첫 글자를 치자마자 "형식이 아닙니다"가 뜬다. 아직 다 쓰지도 않았는데 혼나는 셈이다. 제출할 때만 검사하면 긴 폼을 다 채운 뒤에야 위로 돌아가 여러 칸을 고쳐야 한다.

검증 시점 단계 그림: 세 줄의 이메일 칸에 같은 글자가 입력되고, 입력 중 줄은 첫 글자부터 오류, 포커스 아웃 줄은 칸을 떠날 때 오류, 제출 줄은 제출할 때 오류가 뜬 뒤, 고치면 셋 모두 통과로 바뀐다입력 중포커스 아웃제출 때사건 1/6: 첫 글자 mm이메일 형식이 아닙니다mm사건 2/6: "minji@"까지 입력minji@이메일 형식이 아닙니다minji@minji@사건 3/6: "minji@ex"까지 입력minji@ex이메일 형식이 아닙니다minji@exminji@ex사건 4/6: 다음 칸으로 넘어감(포커스 아웃)minji@ex이메일 형식이 아닙니다minji@ex이메일 형식이 아닙니다minji@ex사건 5/6: 제출 버튼minji@ex이메일 형식이 아닙니다minji@ex이메일 형식이 아닙니다minji@ex이메일 형식이 아닙니다사건 6/6: ".com"을 더해 고침minji@ex.com✓minji@ex.com✓minji@ex.com✓빨간 테두리 = 오류를 보이는 중. 형식은 학습용으로 단순화한 이메일 규칙이다
1 / 6

1. 첫 글자 m → 입력 중: 오류, 포커스 아웃: 표시 없음, 제출 때: 표시 없음.

같은 입력을 세 방식으로 검증했다. 입력 중 검증은 다 쓰기도 전에 혼내고, 제출 때 검증은 너무 늦게 알린다. 포커스 아웃에 알리고 고치는 즉시 지우는 방식이 그 사이다. 표시는 계산 함수로 구했다.

NN/g는 검증을 가능하면 칸 옆에서(인라인) 하되, 대부분의 경우 사용자가 그 칸을 끝내고 다음 칸으로 넘어갈 때까지 오류를 보이지 말라고 한다. Baymard의 결론도 같다. 너무 이른 검증은 경험을 해치므로 칸을 떠날 때나 우편번호·카드 번호처럼 길이가 정해진 값이 다 찼을 때 검사하고, 한번 뜬 오류는 사용자가 고쳐서 맞는 값이 되는 순간 글자 단위로 지운다. 맞게 쓴 칸에 체크 표시를 주는 긍정 검증도 권한다. 다만 NN/g는 성공 표시를 모든 칸에 남발하면 어수선해진다고 덧붙인다. 비밀번호 강도처럼 맞추기 어려운 칸에 주로 쓴다.

시점좋은 점나쁜 점맞는 곳
입력 중(글자마다)즉시 안다다 쓰기 전부터 오류비밀번호 조건 목록처럼 맞춰 가는 칸, 이미 뜬 오류를 지울 때
포커스 아웃다 쓴 뒤 바로 안다칸을 건너뛰기만 해도 오류가 뜰 수 있다대부분의 칸(빈 칸은 제출 때 알린다)
제출쓰는 동안 방해가 없다한꺼번에 몰려 늦게 안다짧은 폼, 서버만 알 수 있는 오류(이미 쓰는 아이디 등)

어느 방식이든 서버에서 다시 검사한다. 화면의 검증은 사용자를 돕는 것이지 막는 장치가 아니다. 요청은 누구나 직접 보낼 수 있다.

오류 메시지 쓰는 법

오류 메시지는 무엇이 틀렸고 어떻게 고치는지 말해야 한다. WCAG 3.3.1(오류 식별, A)은 자동으로 찾은 입력 오류가 있으면 어느 항목이 틀렸는지 밝히고 오류를 글로 설명하라고 요구한다(W3C).

왼쪽 폼은 맨 위에 잘못된 입력입니다 한 줄만 있고 어느 칸이 틀렸는지 모른다. 오른쪽 폼은 맨 위 요약에 틀린 두 칸이 링크로 나열되고, 각 칸 아래에 느낌표 아이콘과 함께 무엇을 어떻게 고치면 되는지 적혀 있다어디가, 왜, 어떻게가 없음잘못된 입력입니다오류 코드 E-1042휴대전화생년월일010-1234-5672026 13 01요약 + 칸마다 고치는 법입력 내용 2곳을 확인하세요· 휴대전화 번호가 한 자리 모자랍니다· 생년월일의 월은 1~12입니다휴대전화010-1234-567!11자리 숫자로 입력하세요생년월일2026 13 01!월은 1에서 12 사이로색만이 아니라 아이콘과 글로도. 입력한 값은 지우지 않고 그대로 둔다
같은 두 오류. 왼쪽은 어느 칸이 왜 틀렸는지 없다. 오른쪽은 맨 위 요약이 각 칸으로 이어지고, 칸 아래에 아이콘과 고치는 법이 있다.
  • 칸 바로 옆에: NN/g는 오류를 해당 칸 옆에 두고, 빨간색에 아이콘을 더해 색을 구분하기 어려운 사람도 찾게 하라고 한다. 아이콘 뒤에 숨겨 마우스를 올려야 보이는 툴팁은 놓치기 쉽다.
  • 요약은 보조로: 맨 위의 오류 요약은 전체를 보여 주지만 그것만으로는 부족하다. GOV.UK는 오류가 하나여도 요약을 두고, 요약의 항목마다 해당 칸으로 가는 링크를 달고, 키보드 포커스를 요약으로 옮기며, 각 칸에도 메시지를 함께 보이라고 한다.
  • 문장은 고칠 방법으로: "잘못된 입력입니다" 대신 "휴대전화 번호가 한 자리 모자랍니다"처럼 무엇이 문제인지, "11자리 숫자로 입력하세요"처럼 무엇을 하면 되는지 쓴다. 사용자를 탓하는 말투("잘못 입력하셨습니다")는 피한다.
  • 입력은 남긴다: 오류가 났다고 칸을 비우면 처음부터 다시 써야 한다. 비밀번호처럼 보안상 지워야 하는 칸이 아니면 그대로 둔다.
  • 반복되면 설계를 의심한다: NN/g는 한 번의 시도에서 같은 오류가 세 번 이상 나오면 사람이 아니라 설계에 문제가 있다는 신호라고 한다.

칸과 메시지는 aria-describedby로 이어 두면 화면 낭독기가 칸에 들어갈 때 안내와 오류를 함께 읽는다(위 레이블 절의 코드). 폼 밖의 오류 화면과 복구는 인터랙션과 상태 노트에 있다.

한 열 레이아웃

NN/g는 칸마다 한 줄씩, 한 열로 쌓으라고 한다. 여러 열은 아래로 내려가던 흐름을 끊는다. 두 열 폼에서는 오른쪽 칸을 건너뛰거나, 다음 칸이 오른쪽인지 아래인지 헷갈린다. 같은 글은 사용성 지침을 따른 폼이 첫 시도에 제출된 비율이 78%로, 따르지 않은 폼의 42%보다 높았다는 2014년 연구(Seckler 외)를 인용한다.

왼쪽 두 열 폼에서는 시선이 왼쪽과 오른쪽을 오가며 지그재그로 내려가고 어느 칸이 다음인지 모호하다. 오른쪽 한 열 폼에서는 시선이 위에서 아래로 곧게 내려간다두 열: 지그재그한 열: 위에서 아래로예외: 시·구, 카드 유효기간의 월·년처럼 짧고 늘 함께 쓰는 칸은 한 줄에 둔다
같은 여섯 칸 남짓을 두 열과 한 열로 놓았다. 두 열에서는 시선이 좌우를 오가고, 한 열에서는 위에서 아래로 곧게 내려간다.

예외는 시·구·우편번호처럼 짧고 논리적으로 붙어 있는 칸이다. 이런 칸은 한 줄에 둘 수 있다. 주제가 두 가지인 폼(배송지와 결제)은 칸을 두 묶음으로 나누고, 묶음에 제목을 달아 화면 낭독기에도 묶음이 드러나게 한다(fieldset과 legend).

단계형 폼과 진행 표시

칸이 많으면 단계를 나눈다. GOV.UK의 질문 페이지 패턴은 한 페이지에 질문 하나를 두면 무엇을 묻는지 이해하기 쉽다고 하고, 서로 관련된 것을 함께 물어야 하면 질문 대신 설명형 제목을 쓰라고 한다. 페이지마다 위에 "이전" 링크를 두되 브라우저의 뒤로 가기도 깨지 않게 하고, 버튼은 "다음"이 아니라 "계속"으로 적는다.

단계형 폼 단계 그림: 위쪽 진행 막대가 네 칸 가운데 지금까지의 칸을 채우고, 아래 화면에는 그 단계의 질문과 입력 칸, 이전 링크와 계속 버튼이 있다배송지연락처결제확인배송지4단계 중 1단계어디로 보낼까요?우편번호주소상세주소계속연락처‹ 이전4단계 중 2단계배송 연락은 어디로 할까요?받는 분휴대전화계속결제‹ 이전4단계 중 3단계어떻게 결제할까요?카드계좌 이체계속확인‹ 이전4단계 중 4단계주문 내용을 확인하세요배송지 · 서울시 …변경연락처 · 010-…변경결제 · 카드변경주문하기
1 / 4

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이 사라지거나 화살표 버튼이 생기는 문제를 피할 수 있다.

휴대폰 세 대의 가상 키보드. 이메일 칸은 글자 키보드에 골뱅이와 점 키가 바로 보이고, 숫자 입력 모드는 0부터 9까지의 큰 숫자판, 전화번호 칸은 별표와 샵이 있는 전화 숫자판이다이메일minji@@.type="email"인증번호48211234567890⌫inputmode="numeric"휴대전화010-123456789*0#type="tel"inputmode는 키보드만 바꾸고 값은 검사하지 않는다. 검사가 필요하면 맞는 type이나 pattern을 함께 쓴다
같은 휴대폰에서 세 칸이 띄우는 키보드. 속성 하나로 사용자가 키보드를 바꾸는 수고가 사라진다. 속성은 위 표와 같은 값에서 가져왔다.

비밀번호 필드: 보기 토글과 붙여넣기

비밀번호 칸은 가려져 있어 오타를 알아채기 어렵다. 그래서 흔히 "비밀번호 확인" 칸을 하나 더 두지만, web.dev는 브라우저가 강한 비밀번호를 제안하는 지금은 두 번 묻는 것이 의미가 없다고 하고, 대신 입력한 글자를 확인할 수 있는 비밀번호 보기 토글을 두라고 한다. 가입 칸에는 autocomplete="new-password", 로그인 칸에는 autocomplete="current-password"를 달아 비밀번호 관리자가 알맞게 제안하고 채우게 한다.

왼쪽 가입 폼은 비밀번호를 두 번 입력하게 하고 붙여넣기를 막는다는 경고가 있다. 오른쪽 폼은 비밀번호 칸 하나에 보기 버튼이 있어 입력한 글자를 확인할 수 있고, 비밀번호 관리자가 강한 비밀번호를 제안한다막고, 두 번 묻기비밀번호••••••••••비밀번호 확인•••••••••붙여넣기를 할 수 없습니다비밀번호가 일치하지 않습니다(어디가?)한 칸 + 보기 + 관리자 허용새 비밀번호15자 이상correct horse batt숨기기비밀번호 관리자xQ7-pL2w-9vBk-rT4m (제안)autocomplete="new-password"붙여넣기·자동 채우기 허용입력을 확인하는 길은 두 번 치기가 아니라 보기 토글이다
두 번 묻고 붙여넣기를 막는 가입 폼과, 한 칸에 보기 토글과 관리자 제안을 받는 폼.

붙여넣기와 자동 채우기를 막지 않는다. 보안 › 인증과 인가 노트에 정리한 미국 표준 기술 연구소의 디지털 신원 지침(NIST SP 800-63B)도 비밀번호 관리자를 쓰면 더 강한 비밀번호를 고르게 된다는 이유로 이를 막지 말라고 하고, 문자 종류를 섞으라는 조합 규칙 대신 길이와 유출 목록 확인을 요구한다. 서버 쪽 저장도 그 노트에 있다. 조건을 안내해야 한다면 쓰기 전에 보이고, 쓰는 동안 하나씩 체크되는 목록이 좋다(입력 중 검증이 맞는 드문 경우다).

날짜 입력

날짜는 쓰임에 따라 입력 방식이 다르다. GOV.UK의 날짜 패턴은 생년월일처럼 외우고 있는 날짜는 일·월·년 세 칸에 숫자로 받으라고 한다. 달력 선택기는 가까운 미래나 최근 과거의 날짜를 고를 때, 날짜와 함께 요일이나 몇째 주인지를 알아야 할 때, 다른 날짜와 견줘 봐야 할 때만 쓰고, 자바스크립트에 기대는 달력만 두지 말고 글자로 입력할 칸을 함께 두라고 한다.

왼쪽은 생년월일을 년, 월, 일 세 칸에 숫자로 입력하는 폼과 예시 안내, 오른쪽은 2026년 11월 달력에서 13일 금요일을 고르는 예약 날짜 선택기와 그 아래 직접 입력 칸이다외우는 날짜: 세 칸생년월일예: 1998 3 27년1998월3일27달력으로 수십 년을 넘기지 않게가까운 날짜 + 요일: 달력2026년 11월일월화수목금토123456789101112131415161718192021222324252627282930직접 입력도 함께2026-11-13 (금)
외우는 날짜는 세 칸으로, 요일이 중요한 가까운 날짜는 달력으로. 한국에서는 년·월·일 순서가 익숙하므로 세 칸도 그 순서로 둔다. 달력의 요일은 계산으로 맞췄다.
  • 순서는 지역에 맞게: GOV.UK는 영국식 일·월·년 순서다. 한국어 서비스는 년·월·일로 두고, 각 칸 위에 "년", "월", "일"을 레이블로 단다.
  • 형식 예시: "예: 1998 3 27"처럼 앞자리 0을 붙이지 않아도 된다는 것까지 보인다.
  • 브라우저 기본 날짜 칸: <input type="date">는 기기에 맞는 선택기를 공짜로 주지만 모양과 형식을 브라우저가 정한다. 생년월일처럼 먼 날짜에는 연도를 한참 넘겨야 해서 불편할 수 있다.

한국 주소와 전화번호

한국 주소는 2014년 1월 1일부터 도로명과 건물번호로 위치를 나타내는 도로명주소를 본격적으로 쓰고, 표기는 시·도, 시·군·구, 도로명과 건물번호, 상세주소(동·층·호), 괄호 안 참고항목 순이다(위키백과). 우편번호는 2015년 8월 1일부터 국가기초구역번호 다섯 자리를 쓴다. 그 전의 여섯 자리(예: 122-090) 우편번호는 이때 폐지됐다(위키백과).

왼쪽은 우편번호 칸과 주소 검색 버튼, 검색으로 채워진 도로명주소, 직접 입력하는 상세주소 칸으로 된 주소 폼이다. 오른쪽은 숫자만 입력한 전화번호 네 개가 휴대전화, 서울, 경기 지역번호에 맞춰 하이픈이 들어간 모양으로 보이는 표다주소: 검색으로 채우고 상세만 쓰기우편번호04500주소 검색5자리 ✓도로명주소서울특별시 중구 예시로 12상세주소 (동·층·호)101동 1203호검색 뒤 포커스를 상세주소로 옮긴다전화번호: 숫자만 받기입력(저장)보이는 형식01012345678010-1234-5678021234567802-1234-567802123456702-123-45670311234567031-123-456702만 두 자리 지역번호주소·번호는 예시다. 하이픈·공백을 넣어 붙여넣어도 받아들이고 숫자만 남긴다
주소는 검색으로 우편번호와 도로명주소를 채우고 상세주소만 직접 쓰게 한다. 전화번호는 숫자만 받아 저장하고, 보일 때 지역번호에 맞춰 하이픈을 넣는다. 번호 형식은 계산 함수로 만들었다.
  • 주소 검색: 국내 서비스는 대개 주소 검색 창으로 우편번호와 도로명주소를 한 번에 채우고, 그 뒤 포커스를 상세주소 칸으로 옮긴다. 사람이 우편번호를 외워 쓰는 일은 드물다. 검색이 실패하거나 해외 주소를 받아야 할 때를 위해 직접 입력하는 길도 남긴다.
  • 칸 나누기: 기본 주소와 상세주소를 나누면 상세주소 빠뜨림이 줄어든다. 자동 채우기에는 postal-code, address-line1, address-line2를 단다.
  • 전화번호: 휴대전화는 010으로 시작하고 서울 지역번호는 02다(위키백과). 칸은 하나로 두고 type="tel", autocomplete="tel"을 단다. 앞자리·가운데·끝을 세 칸으로 나누면 붙여넣기와 자동 채우기가 깨진다. 하이픈·공백을 섞어 써도 받아서 숫자만 남기고, 보여 줄 때 형식을 맞춘다.
  • 이름: 한국 이름은 성이 먼저라 "성"과 "이름" 두 칸 순서가 서양 폼과 반대다. 한 칸(autocomplete="name")으로 받는 편이 단순하다.

버튼 문구와 이중 제출 방지

제출 버튼의 문구는 누르면 일어날 일을 말한다. web.dev는 "제출" 대신 "로그인"·"계정 만들기"를 쓰라고 하고, GOV.UK는 정보를 저장한다면 "저장하고 계속", 저장하지 않는다면 그냥 "계속"처럼 실제 동작에 맞는 동사를 고르라고 한다. 결제 버튼이라면 "결제하기"보다 "29,000원 결제하기"가 더 분명하다.

결제 버튼을 0, 150, 300, 1400밀리초에 눌렀을 때의 시간표. 막지 않으면 결제 요청이 네 번 나가고, 응답이 올 때까지 버튼을 막으면 0밀리초와 1400밀리초의 두 번만 나간다누른 시각막지 않음4건응답 전엔 막음2건0ms500ms1000ms1500ms2000ms막대 = 결제 한 건의 처리 시간 1200ms. 서버도 멱등 키로 한 번 더 막는다
응답이 1.2초 걸리는 동안 결제 버튼을 네 번 누른 경우. 막지 않으면 결제 요청이 네 번 나간다. 응답이 올 때까지 막으면 처음과, 응답 뒤에 다시 누른 두 번만 나간다. 시각은 계산 함수로 구했다.

느린 연결에서 반응이 없으면 사람은 버튼을 다시 누른다. 결제·주문 폼에서는 이것이 중복 결제가 된다. 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, 한 번 누른 뒤 버튼 막기.