지난 편에서 데이터를 보여주는 표를 올바르게 만드는 법을 익혔습니다. 이번에는 방향을 바꿔, 사용자로부터 정보를 입력받는 폼을 다룹니다. 표가 화면에 데이터를 내보내는 창구라면, 폼은 사용자가 우리에게 데이터를 들여보내는 창구입니다. 로그인, 회원가입, 검색, 댓글 작성까지 웹에서 사용자와 주고받는 거의 모든 대화가 폼을 통해 이루어집니다.
제가 초보 시절 폼을 만들 때 가장 많이 저지른 실수는, 입력창을 그냥 네모 상자처럼 여기고 아무 표시 없이 늘어놓은 것이었습니다. 눈으로 보는 사람에게는 그럭저럭 통했지만, 소리로 듣는 사람이나 자동완성을 쓰는 사람에게는 정체불명의 빈칸일 뿐이었습니다. 이번 편에서는 폼의 뼈대를 세우는 법, 입력창에 이름표를 붙이는 법, 그리고 상황에 맞는 입력 타입을 고르는 법을 차례로 살펴보겠습니다.
1. 폼의 뼈대 세우기
폼의 가장 바깥에는 폼 전체를 감싸는 상자가 있습니다. 이 상자는 안에 담긴 입력값들을 하나로 묶어, 사용자가 제출할 때 어디로 어떤 방식으로 보낼지를 정합니다. 목적지를 지정하는 속성과, 보내는 방식을 지정하는 속성이 이 상자의 두 가지 핵심 정보입니다. 이 둘이 있어야 폼이 비로소 자기 역할을 합니다.
보내는 방식은 크게 두 갈래입니다. 하나는 입력값을 주소 뒤에 붙여 보내는 방식으로, 검색처럼 결과를 주소로 공유하거나 즐겨찾기 할 수 있어야 하는 경우에 어울립니다. 다른 하나는 입력값을 본문에 담아 보내는 방식으로, 비밀번호나 긴 글처럼 주소에 노출되면 곤란한 내용에 씁니다. 이 구분을 처음부터 의식하면 나중에 보안 문제로 고생할 일이 줄어듭니다.
각 입력창에는 이름을 붙여야 합니다. 이 이름은 제출된 값이 서버에서 무엇인지 알아보게 하는 꼬리표입니다. 이름이 없는 입력창은 값을 아무리 잘 받아도 서버로 전달되지 않아 유령처럼 사라집니다. 그래서 저는 입력창을 만들 때 이름을 붙이는 것을 반드시 챙기는 습관을 들였습니다.
폼 안에서 제출을 담당하는 버튼도 필요합니다. 이 버튼을 누르면 폼이 값을 모아 목적지로 보냅니다. 버튼의 종류를 분명히 지정하지 않으면 뜻하지 않게 폼이 제출되어 페이지가 새로 고쳐지는 일이 생깁니다. 제가 겪은 흔한 함정 중 하나가 바로 이것이라, 버튼의 역할을 항상 명시하게 되었습니다.
아래는 가장 기본적인 폼의 뼈대입니다. 목적지와 방식, 이름, 그리고 제출 버튼이 어떻게 배치되는지 살펴보세요.
<form action="/search" method="get">
<input type="text" name="q">
<button type="submit">검색</button>
</form>
2. 라벨, 입력창의 이름표
폼에서 제가 가장 강조하고 싶은 원칙은, 모든 입력창에 이름표를 붙이라는 것입니다. 여기서 이름표란 이 칸이 무엇을 입력하는 곳인지 알려주는 짧은 글자입니다. 이 이름표를 입력창과 정식으로 연결하면, 화면 낭독 프로그램은 입력창에 초점이 갈 때마다 이름표를 함께 읽어 줍니다. 소리로 듣는 사용자가 지금 무슨 칸에 있는지 알게 되는 것이죠.
이름표와 입력창을 연결하는 방법은 두 가지입니다. 하나는 입력창에 고유한 식별자를 주고, 이름표가 그 식별자를 지목하게 하는 것입니다. 다른 하나는 이름표가 입력창을 아예 품 안에 감싸 안는 것입니다. 어느 쪽이든 연결만 확실하면 되고, 저는 감싸는 방식이 식별자 관리가 필요 없어 더 편할 때가 많았습니다.
여기서 흔히 저지르는 오해가 있습니다. 입력창 안에 흐릿하게 뜨는 안내 글자를 이름표 대신으로 여기는 것입니다. 그 흐릿한 글자는 입력을 시작하면 사라지기 때문에, 무엇을 넣던 칸이었는지 잊게 만듭니다. 게다가 화면 낭독 프로그램이 그것을 이름표로 안내한다는 보장도 없습니다. 그러니 그 안내 글자는 어디까지나 보조일 뿐, 이름표를 대체할 수 없습니다.
이름표는 눈으로 보는 사용자에게도 중요합니다. 이름표를 누르면 연결된 입력창에 초점이 옮겨가기 때문에, 특히 작은 동그라미 선택 항목처럼 정확히 누르기 어려운 컨트롤에서 누를 수 있는 범위를 넓혀 줍니다. 손이 떨리거나 화면이 작은 환경에서 이 넓은 누름 범위는 큰 차이를 만듭니다.
제가 이름표를 제대로 붙이기 시작한 뒤로, 폼의 사용성 문제 신고가 눈에 띄게 줄었습니다. 그 전에는 사용자들이 어느 칸이 무엇인지 헷갈린다는 이야기를 종종 들었는데, 이름표 하나로 그 혼란이 사라진 것입니다. 작은 습관이지만 효과는 결코 작지 않았습니다.
정리하면, 입력창을 만들 때 가장 먼저 할 일은 이름표를 붙이고 그것을 입력창과 연결하는 것입니다. 이 한 단계가 폼 접근성의 절반을 책임진다고 해도 지나치지 않습니다. 흐릿한 안내 글자에 의존하지 말고, 반드시 정식 이름표를 두시길 권합니다.
3. 입력 타입 총정리
입력창에는 여러 종류가 있고, 각각 받으려는 정보에 맞는 타입이 정해져 있습니다. 가장 기본은 아무 글자나 받는 일반 텍스트지만, 이메일을 받을 때는 이메일 타입을, 전화번호는 전화 타입을, 주소는 주소 타입을, 숫자는 숫자 타입을 쓰는 식으로 구분합니다. 타입을 정확히 고르는 것만으로도 사용자 경험이 크게 좋아집니다.
가장 눈에 띄는 이점은 모바일 자판입니다. 이메일 타입을 지정하면 스마트폰에서 골뱅이 기호가 있는 자판이 뜨고, 숫자 타입이면 숫자판이, 전화 타입이면 전화 다이얼 자판이 뜹니다. 사용자가 자판을 전환하는 수고를 덜어 주는 것이죠. 저는 이 작은 배려가 폼 완료율을 실제로 끌어올리는 것을 여러 번 확인했습니다.
또 다른 이점은 기본 검증입니다. 이메일 타입은 골뱅이가 없는 값을 제출하려 하면 브라우저가 자체적으로 막아 줍니다. 주소 타입도 형식에 맞지 않으면 경고를 띄웁니다. 우리가 직접 검사 코드를 짜지 않아도 브라우저가 1차로 걸러 주니, 그만큼 코드가 가벼워집니다.
날짜나 시간을 받을 때는 전용 타입을 쓰면 브라우저가 달력이나 시간 선택기를 띄워 줍니다. 사용자가 형식을 고민하며 직접 입력하는 대신 골라서 선택하니 오류가 줄어듭니다. 비밀번호 타입은 입력한 글자를 점으로 가려 어깨너머로 훔쳐보는 것을 막아 줍니다. 이렇게 타입마다 고유한 편의 기능이 딸려 옵니다.
아래 예시는 자주 쓰는 입력 타입들을 모아 놓은 것입니다. 각 칸에 맞는 타입이 지정된 것을 눈여겨보세요.
<input type="email" name="email">
<input type="tel" name="phone">
<input type="number" name="age">
<input type="date" name="birth">
<input type="password" name="pw">
4. 선택형 컨트롤과 묶음
정보를 받는 방식이 자유 입력만 있는 것은 아닙니다. 정해진 선택지 중에서 하나만 고르게 하거나, 여럿을 고르게 하거나, 목록에서 하나를 펼쳐 고르게 하는 컨트롤도 있습니다. 이런 선택형 컨트롤을 잘 쓰면 사용자가 잘못된 값을 넣을 여지 자체가 사라져, 검증 부담이 크게 줄어듭니다.
하나만 고르는 동그라미 선택 항목은 같은 이름으로 여러 개를 묶어야 서로 배타적으로 동작합니다. 이름이 같아야 브라우저가 이들을 한 무리로 인식해, 하나를 고르면 나머지가 자동으로 풀립니다. 이름이 제각각이면 여러 개가 동시에 선택되는 이상한 일이 벌어집니다. 제가 초보 때 헤맸던 지점이기도 합니다.
여럿을 고를 수 있는 네모 선택 항목은 각각 독립적으로 켜고 끕니다. 약관 동의나 관심사 다중 선택처럼 여러 개를 함께 고를 수 있는 경우에 씁니다. 목록을 펼쳐 고르는 컨트롤은 선택지가 많을 때 화면을 절약하는 데 좋습니다. 선택지의 수와 성격에 따라 알맞은 컨트롤을 고르는 안목이 필요합니다.
이런 선택형 컨트롤이 여러 개 모여 하나의 질문을 이룰 때는, 이들을 하나의 묶음으로 감싸고 그 묶음에 제목을 달아야 합니다. 예를 들어 알림을 받을지 말지 고르는 동그라미 선택 항목 두 개는, 알림 수신이라는 제목 아래 묶여야 그 두 선택지가 한 질문에 대한 답이라는 것이 드러납니다.
이 묶음과 제목이 왜 중요한지는 소리로 폼을 들어보면 압니다. 묶음의 제목이 없으면 화면 낭독 프로그램은 그냥 전체, 안 받음이라는 선택지만 읽어, 무엇에 대한 선택인지 알 수 없습니다. 묶음 제목이 있으면 알림 수신, 전체처럼 맥락과 함께 읽혀 이해가 됩니다. 소리로 듣는 사용자에게는 이 제목이 생명줄입니다.
아래는 선택 항목을 묶고 제목을 단 예시입니다. 묶음과 제목의 관계에 주목해 보세요.
<fieldset>
<legend>알림 수신</legend>
<label><input type="radio" name="notify" value="all"> 전체</label>
<label><input type="radio" name="notify" value="none"> 안 받음</label>
</fieldset>
5. 버튼과 자동완성
폼의 마지막 단추는 말 그대로 버튼입니다. 폼 안의 버튼은 역할을 분명히 지정해야 합니다. 제출용인지, 값을 초기화하는 용도인지, 아니면 그냥 어떤 동작을 실행하는 일반 버튼인지를 밝히지 않으면, 많은 브라우저가 이를 제출 버튼으로 간주해 예기치 않게 폼을 보내 버립니다. 저는 이 기본 동작에 여러 번 당한 뒤로 역할을 항상 명시합니다.
자동완성은 사용자의 시간을 크게 아껴 주는 기능입니다. 각 입력창에 이 칸이 이메일인지, 이름인지, 주소인지를 알리는 표시를 붙이면, 브라우저가 이전에 저장한 값으로 자동 채우기를 제안합니다. 사용자가 매번 같은 정보를 타이핑하지 않아도 되니, 특히 모바일에서 폼 완료가 훨씬 수월해집니다.
자동완성 표시는 로그인 상황에서 특히 중요합니다. 아이디 칸과 비밀번호 칸에 각각 올바른 표시를 달면, 브라우저와 비밀번호 관리자가 두 칸을 정확히 알아보고 저장된 자격 정보를 채워 줍니다. 표시가 없거나 잘못되면 자동 채우기가 엉뚱한 칸에 들어가거나 아예 동작하지 않습니다.
다만 자동완성이 곤란한 경우도 있습니다. 일회용 인증번호처럼 매번 새로 입력해야 하는 값이나, 공용 컴퓨터에서 저장되면 안 되는 민감한 값에는 자동완성을 꺼야 할 때가 있습니다. 상황에 맞게 켜고 끄는 판단이 필요하지, 무조건 켜거나 무조건 끄는 것은 답이 아닙니다.
제가 자동완성 표시를 꼼꼼히 챙긴 뒤로, 사용자들이 폼을 훨씬 빠르게 완료한다는 것을 데이터로 확인했습니다. 표시 하나 붙이는 데 몇 초면 되는데, 그 효과는 수많은 사용자의 시간을 아껴 주는 것으로 돌아왔습니다. 작은 정성이 큰 편의로 이어진 사례였습니다.
이번 편에서는 폼의 뼈대와 이름표, 입력 타입, 선택형 컨트롤, 그리고 버튼과 자동완성까지 폼의 기초를 두루 살펴봤습니다. 다음 편에서는 사용자가 넣은 값이 올바른지 확인하는 유효성 검사로 넘어가겠습니다. 입력을 받는 법을 익혔으니, 이제 그 입력이 규칙에 맞는지 검사하는 법을 배울 차례입니다.