앞 편에서 화면 낭독기를 이해했다면, 이제 낭독기 사용자가 가장 자주 애를 먹는 곳을 살펴볼 차례다. 바로 폼이다. 로그인, 회원가입, 검색, 결제까지 웹에서 무언가를 하려면 대개 폼을 거친다. 그런데 폼은 접근성이 무너지기 가장 쉬운 곳이기도 하다. 이번 편은 누구나 채울 수 있는 폼을 만드는 법을 다룬다.
나는 예전에 입력 칸에 안내 문구만 흐리게 넣고 라벨을 생략한 폼을 만들었다. 화면은 깔끔했지만, 낭독기로 들어보니 이름 없는 입력 칸만 줄줄이 나올 뿐이었다. 무엇을 입력하라는 건지 알 수 없는 폼이었다. 그 뒤로 나는 폼을 만들 때 겉모습보다 각 칸이 무엇인지 또렷이 전하는 것을 먼저 챙기게 됐다.
1. 라벨은 반드시 연결한다
폼 접근성의 기본은 모든 입력 칸에 라벨을 연결하는 것이다. 라벨 요소 label은 이 칸이 무엇을 입력하는 곳인지 알려주는 이름표다. 라벨을 연결하면 낭독기가 칸에 포커스했을 때 이 칸의 이름을 읽어 준다. 나는 입력 칸을 하나 만들 때마다 라벨을 짝으로 챙기며, 이름표 없는 칸이 하나도 없게 한다. 라벨은 폼의 기본 중 기본이다.
라벨을 칸에 연결하는 방법은 두 가지다. 라벨의 연결 속성 for에 칸의 식별자를 담아 명시적으로 잇거나, 라벨이 칸을 통째로 감싸 암묵적으로 잇는다. 어느 쪽이든 라벨과 칸이 하나로 묶인다. 나는 상황에 맞는 방법을 골라 쓰되, 둘 중 무엇을 쓰든 라벨과 칸이 확실히 연결됐는지를 반드시 확인한다. 연결이 안 되면 이름표가 없는 것과 같다.
라벨이 연결되면 눈으로 보는 사람에게도 이롭다. 라벨을 클릭하면 연결된 칸에 포커스가 가고, 체크박스 라벨을 누르면 체크가 된다. 클릭할 수 있는 영역이 넓어져 조작이 편해지는 것이다. 나는 라벨 연결이 접근성뿐 아니라 모두의 조작감을 높인다는 걸 겪으며, 라벨을 귀찮은 형식이 아니라 실질적인 이득으로 대하게 됐다.
가장 흔한 실수가 안내 문구로 라벨을 대신하는 것이다. 칸 안에 흐리게 뜨는 안내 문구 placeholder는 라벨의 대용이 아니다. 입력을 시작하면 사라지고, 대비도 낮아 잘 안 보이며, 낭독기가 라벨로 취급하지 않을 수도 있다. 나는 안내 문구를 라벨 대신 쓰지 않고, 라벨은 라벨대로 두고 안내 문구는 보조로만 쓴다.
안내 문구는 예시나 형식을 보여주는 보조 역할에 어울린다. 라벨로 이 칸이 무엇인지 알리고, 안내 문구로 어떻게 입력하는지 예를 보여주는 식이다. 둘의 역할이 다르다. 나는 라벨과 안내 문구를 각각 제자리에 두어, 라벨이 사라지는 일 없이 칸의 이름이 늘 남아 있게 한다. 안내 문구는 라벨을 거들 뿐 대신하지 못한다.
정리하면 모든 입력 칸에 라벨을 연결하는 것이 폼 접근성의 기본이고, 명시적이든 암묵적이든 확실히 이어야 하며, 안내 문구는 라벨의 대용이 될 수 없다. 나는 이름표 없는 칸을 두지 않는다. 라벨 하나를 빠뜨리면 그 칸은 낭독기 사용자에게 정체불명의 빈칸이 되고 만다. 이름을 챙겼다면, 다음은 그 칸이 꼭 채워야 하는 것인지를 알리는 문제다.
2. 필수 입력을 알린다
어떤 칸은 반드시 채워야 하고 어떤 칸은 선택이다. 이 구분을 사용자에게 알려야 한다. 눈으로 보는 사람에게는 흔히 별표로 필수를 표시한다. 그런데 이 별표만으로는 낭독기 사용자에게 잘 전해지지 않을 수 있다. 나는 필수 입력을 시각과 낭독 양쪽으로 함께 알리는 것을 원칙으로 삼는다. 한쪽만으로는 부족하다.
낭독기에 필수임을 알리는 방법은 입력 요소에 필수 속성 required를 붙이는 것이다. 이 속성이 있으면 낭독기가 이 칸이 필수라고 읽어 주고, 브라우저의 기본 검증도 함께 작동한다. 나는 필수 칸에 이 속성을 붙여, 낭독기 사용자도 이 칸을 반드시 채워야 한다는 것을 소리로 알게 한다. 시각의 별표와 이 속성을 짝으로 둔다.
비슷한 역할을 하는 아리아 속성 aria-required도 있다. 네이티브 필수 속성을 쓸 수 없는 커스텀 입력에서 필수임을 알릴 때 쓴다. 다만 네이티브 속성으로 되는 경우에는 그것을 먼저 쓰는 것이 낫다. 나는 요소가 먼저, 아리아가 나중이라는 원칙을 여기서도 지켜, 네이티브 속성으로 안 될 때만 이 아리아 속성을 쓴다.
필수 표시를 별표로만 할 때는 그 별표의 뜻을 미리 알려야 한다. 폼 어딘가에 별표는 필수 항목이라는 안내가 있어야 사용자가 별표의 의미를 안다. 나는 별표를 쓰는 폼에는 그 뜻을 밝히는 안내를 함께 두고, 별표 자체도 낭독기가 무시하지 않게 처리하거나 속성으로 필수를 따로 알린다. 표시의 뜻이 통해야 표시가 제구실을 한다.
선택 항목이 적고 필수가 많을 때는 반대로 표시하는 것이 나을 수도 있다. 필수마다 별표를 다는 대신, 드문 선택 항목에만 선택이라고 밝히는 것이다. 어느 쪽이든 사용자가 무엇을 꼭 채워야 하는지 헷갈리지 않게 하는 것이 목적이다. 나는 폼의 성격에 맞게 표시 방식을 정하되, 필수와 선택의 구분이 또렷하게 드러나게 한다.
정리하면 필수 입력은 시각의 별표와 낭독을 위한 속성으로 함께 알리고, 네이티브 필수 속성을 우선하며, 별표의 뜻을 미리 밝힌다. 나는 필수를 양쪽으로 알린다. 그런데 사용자가 잘못 입력했을 때, 그 오류를 알리는 것이야말로 폼 접근성의 어려운 관문이다.
3. 오류를 제대로 알린다
사용자가 잘못 입력하면 무엇이 왜 잘못됐는지 알려야 한다. 그런데 오류를 칸의 색만 빨갛게 바꿔 표시하면, 색을 못 보는 사용자나 낭독기 사용자는 오류를 알아채지 못한다. 나는 오류를 색이 아니라 텍스트로 알리는 것을 기본으로 삼는다. 무엇이 잘못됐고 어떻게 고쳐야 하는지를 글로 분명히 전해야 한다.
오류가 난 칸에는 잘못됐음을 알리는 속성 aria-invalid를 붙인다. 이 속성이 켜지면 낭독기가 이 칸에 오류가 있다고 알린다. 그리고 오류 메시지를 부가 설명 연결 속성 aria-describedby로 칸에 이어 붙인다. 그러면 낭독기가 칸에 포커스했을 때 오류 내용까지 읽어 준다. 나는 이 두 속성을 짝으로 써서 오류를 낭독기에 온전히 전한다.
오류 메시지는 원인과 해결책을 함께 담아야 한다. 잘못됐습니다라는 말만으로는 무엇을 어떻게 고쳐야 할지 알 수 없다. 이메일 형식이 올바르지 않으니 골뱅이 기호를 포함해 달라는 식으로, 구체적으로 알려야 한다. 나는 오류 메시지를 쓸 때 사용자가 그 메시지만 읽고도 바로 고칠 수 있는지를 기준으로 삼는다. 친절한 오류가 좋은 오류다.
오류 메시지는 색에만 기대지 않는다. 빨간 글자에 더해 경고 아이콘과 분명한 문장을 함께 두어, 색을 못 봐도 오류임을 알게 한다. 앞서 색 대비 편에서 말한 색에만 기대지 않기 원칙이 폼에서 특히 중요하다. 나는 오류를 색과 아이콘과 텍스트라는 세 가지 단서로 함께 알려, 어떤 사용자도 오류를 놓치지 않게 한다.
오류가 언제 뜨는지도 신경 쓸 부분이다. 입력하는 중간마다 오류를 띄우면 아직 다 안 쳤는데 잘못됐다고 다그치는 셈이 된다. 나는 대개 칸을 벗어날 때나 제출할 때 오류를 확인하고, 사용자가 고치면 오류를 곧바로 거둔다. 오류를 알리는 시점과 거두는 시점을 알맞게 잡는 것도 폼 경험의 일부다.
정리하면 오류는 색이 아니라 텍스트로 알리고, 잘못됨을 알리는 속성과 오류 메시지를 연결하는 속성을 짝으로 쓰며, 원인과 해결책을 담고, 색과 아이콘과 텍스트로 함께 전한다. 나는 친절하고 또렷한 오류를 만든다. 다음은 여러 입력을 하나로 묶는 문제다.
4. 관련 입력을 묶는다
라디오 버튼이나 체크박스처럼 여러 선택지가 하나의 질문에 속할 때는, 그것들을 묶어 그룹의 이름을 알려야 한다. 성별을 고르는 라디오 버튼 세 개가 있으면, 각 버튼의 라벨만으로는 이 셋이 성별 질문에 속한다는 것을 알 수 없다. 나는 관련된 입력을 묶어 그룹에 이름을 주는 것을 폼 접근성의 중요한 부분으로 여긴다.
이 묶음을 만드는 요소가 fieldset이고, 그 그룹의 이름을 담는 요소가 legend다. 관련 입력들을 묶음 요소로 감싸고, 그 안에 이름 요소로 그룹 제목을 두면, 낭독기가 각 선택지를 읽을 때 이 그룹의 이름도 함께 알린다. 나는 라디오 버튼이나 체크박스 그룹을 만들 때 이 두 요소로 묶어, 각 선택지가 어느 질문에 속하는지 또렷이 전한다.
이 묶음이 없으면 낭독기 사용자는 선택지의 맥락을 잃는다. 이메일과 문자라는 두 라디오 버튼만 들리면, 이것이 무엇을 고르는 것인지 알 수 없다. 그룹 이름으로 알림 방식이라는 제목이 함께 들려야 비로소 맥락이 선다. 나는 이 맥락을 주는 것이 흩어진 선택지를 하나의 질문으로 묶어 주는 일이라는 걸 이해하고 습관으로 삼았다.
이 그룹 묶음을 상자 요소로 흉내 내려 하지 않는 것이 좋다. 상자로 감싸고 아리아로 이름을 붙일 수도 있지만, 네이티브 묶음 요소가 있으니 그것을 먼저 쓰는 것이 낫다. 나는 요소가 먼저라는 원칙을 여기서도 지켜, 그룹 묶음에는 상자가 아니라 전용 묶음 요소를 쓴다. 네이티브 요소가 맥락을 가장 확실히 전한다.
모든 입력을 묶어야 하는 것은 아니다. 하나만으로 뜻이 통하는 입력에는 묶음이 필요 없다. 여러 선택지가 하나의 질문에 속할 때만 묶는다. 나는 이 입력들이 하나의 질문에 딸린 것인지를 기준으로 묶을지 말지를 정한다. 필요 없는 곳에 묶음을 남발하면 오히려 구조가 복잡해지니, 필요한 곳에만 쓴다.
정리하면 하나의 질문에 속한 여러 선택지는 묶음 요소로 감싸고 이름 요소로 그룹 제목을 주어 맥락을 전하며, 이때도 네이티브 요소를 우선하고 필요한 곳에만 묶는다. 나는 라디오와 체크박스 그룹을 이렇게 묶는다. 마지막으로, 폼을 제출했다 실패했을 때의 처리를 살펴보자.
5. 제출 실패를 다룬다
폼을 제출했는데 오류로 실패하면, 사용자를 문제로 안내해야 한다. 각 칸마다 오류를 표시하는 것만으로는 부족하다. 오류가 여러 개면 사용자가 그것을 하나하나 찾아 헤매게 된다. 나는 제출이 실패했을 때 오류를 한데 모은 요약을 화면 위에 보여주어, 무엇이 잘못됐는지 한눈에 알 수 있게 한다.
이 오류 요약에는 잘못된 항목들을 나열하고, 각 항목을 해당 칸으로 이동하는 링크로 만든다. 그러면 사용자가 요약에서 문제를 파악하고, 링크를 눌러 문제의 칸으로 바로 갈 수 있다. 나는 오류 요약을 문제 목록이자 이동 통로로 만들어, 사용자가 오류를 찾아 헤매지 않고 곧장 고치러 가게 한다. 요약이 길잡이 역할을 한다.
제출이 실패하면 이 오류 요약으로 포커스를 옮긴다. 앞서 포커스 편에서 말했듯, 화면이 크게 바뀔 때는 포커스를 알맞은 곳으로 옮겨야 한다. 오류 요약으로 포커스를 옮기면 낭독기 사용자가 곧바로 무엇이 잘못됐는지 듣는다. 나는 실패한 제출에서 오류 요약으로 포커스를 점프시켜, 키보드와 낭독기 사용자를 문제로 데려간다.
낭독기 사용자에게는 오류가 났다는 사실 자체도 소리로 알려야 한다. 화면에 오류 요약이 떠도 낭독기 사용자가 그것을 못 들으면 소용없다. 그래서 오류 요약을 낭독기가 즉시 읽는 알림 영역으로 만들거나, 그곳으로 포커스를 옮겨 읽히게 한다. 나는 오류가 발생한 순간이 낭독기 사용자에게 조용히 지나가지 않도록 챙긴다.
제출을 막을 때는 무엇 때문에 막혔는지가 분명해야 한다. 버튼을 눌렀는데 아무 반응 없이 제출이 안 되면, 사용자는 왜 안 되는지 몰라 답답해한다. 나는 제출이 막히는 모든 경우에 그 이유를 오류로 분명히 알린다. 조용한 실패는 사용자를 미궁에 빠뜨리니, 실패에는 항상 까닭이 따라야 한다.
정리하면 제출이 실패하면 오류를 모은 요약을 위에 보여주고 각 항목을 칸으로 잇는 링크로 만들며, 그 요약으로 포커스를 옮기고 낭독기에 오류를 알리며, 실패에는 늘 까닭을 붙인다. 나는 실패한 폼도 친절하게 다룬다. 다음 편에서는 폼을 넘어 이미지와 미디어 전반의 접근성을 살펴본다.