적용 방법
접근 가능한 폼 만들기
입력 필드의 레이블 연결, 그룹 묶기, 필수 표시, 입력 형식 안내, 오류 표시와 연결, autocomplete, 오류 요약과 포커스 이동까지 폼 접근성의 핵심을 정리합니다.
레이블 연결하기
모든 입력 필드에는 무엇을 입력하는지 알려 주는 레이블이 필요합니다(WCAG 3.3.2 레이블 또는 지시사항). 레이블은 화면에 보여야 하고, 동시에 프로그램으로 입력 필드와 연결되어 있어야 화면낭독기가 필드에 포커스가 갈 때 함께 읽습니다(WCAG 1.3.1, 4.1.2).
가장 확실한 방법은 <label> 요소입니다. for 속성에 입력 필드의 id를 쓰거나, 입력 필드를 <label> 안에 넣습니다. 연결된 레이블을 누르면 입력 필드에 포커스가 가므로 작은 체크박스·라디오 버튼의 클릭 영역도 넓어집니다.
<span>이메일</span>
<input type="email" id="email">
<!-- 이름: 없음 --><label for="email">이메일</label>
<input type="email" id="email">
<!-- 또는 감싸기 -->
<label>
<input type="checkbox" name="agree"> 약관에 동의합니다
</label>- 보이는 레이블을 둘 수 없는 검색창 등에서는
aria-label을, 화면의 다른 텍스트를 레이블로 쓰려면aria-labelledby를 씁니다. 이름이 정해지는 우선순위는 접근 가능한 이름 계산을 참고하세요. placeholder는 레이블을 대신하지 않습니다. 입력을 시작하면 사라지고, 대비가 낮은 경우가 많습니다.- 레이블은 필드와 가까이, 일관된 위치(보통 위쪽 또는 왼쪽, 체크박스·라디오는 오른쪽)에 둡니다.
관련 컨트롤 묶기: fieldset과 legend
라디오 버튼 묶음, 체크박스 묶음, 주소·생년월일처럼 여러 필드가 하나의 질문에 속하면 <fieldset>으로 묶고 <legend>로 질문을 씁니다. <fieldset>은 group 역할이 되고 <legend>가 그 이름이 되어, 화면낭독기는 그룹에 처음 들어갈 때 “배송 방법, 그룹”처럼 질문을 함께 알려 줍니다.
<fieldset>
<legend>배송 방법</legend>
<label><input type="radio" name="ship" value="normal" checked> 일반 배송</label>
<label><input type="radio" name="ship" value="fast"> 빠른 배송 (+3,000원)</label>
</fieldset><fieldset>을 쓸 수 없는 커스텀 컴포넌트라면 role="group"(라디오는 radiogroup)에 aria-labelledby로 질문 텍스트를 연결합니다.
<p id="birth-label">생년월일</p>
<div role="group" aria-labelledby="birth-label">
<label for="by">년</label><input id="by" inputmode="numeric" autocomplete="bday-year">
<label for="bm">월</label><input id="bm" inputmode="numeric" autocomplete="bday-month">
<label for="bd">일</label><input id="bd" inputmode="numeric" autocomplete="bday-day">
</div>필수 입력 표시: required와 aria-required
필수 항목은 화면에서 눈에 보이게 표시하고, 프로그램으로도 필수임을 알려야 합니다.
| 구분 | required (HTML) | aria-required="true" |
|---|---|---|
| 보조기술에 “필수” 알림 | 예 | 예 |
| 브라우저 기본 검사·제출 차단 | 예 (novalidate가 없을 때) | 아니요 |
:required/:invalid CSS 선택자 | 예 | 아니요 |
| 쓰는 곳 | 네이티브 <input>, <select>, <textarea> | combobox, listbox, radiogroup 같은 커스텀 위젯 |
네이티브 입력 필드에는 required를 씁니다. 브라우저의 기본 오류 말풍선 대신 직접 만든 오류 메시지를 보여 주려면 <form novalidate>로 기본 검사를 끄고 required는 그대로 둡니다. 두 속성을 함께 쓸 필요는 없습니다(aria-required 참고).
<p>표시(<span aria-hidden="true">*</span><span class="sr-only">별표</span>)는 필수 항목입니다.</p>
<label for="name">이름 <span aria-hidden="true">*</span></label>
<input id="name" name="name" required autocomplete="name">별표(*)의 의미를 알려 주세요
별표만으로는 의미를 모를 수 있으므로 폼 앞에 “*는 필수 항목”이라고 안내하거나, 레이블에 “(필수)”를 텍스트로 씁니다. 별표를 aria-hidden="true"로 숨기면 required가 이미 “필수”를 알리므로 “별표”가 중복해서 읽히지 않습니다. 빨간색만으로 필수를 구분하는 것은 WCAG 1.4.1 색 사용 위반입니다.
입력 형식 안내
비밀번호 규칙, 날짜 형식처럼 입력 전에 알아야 하는 정보는 레이블 근처에 보이는 텍스트로 쓰고 aria-describedby로 입력 필드와 연결합니다. 그러면 필드에 포커스가 갈 때 이름 뒤에 설명이 읽힙니다(설명 연결하기 참고).
<label for="pw">비밀번호</label>
<input type="password" id="pw" required
autocomplete="new-password" aria-describedby="pw-hint">
<p id="pw-hint">영문, 숫자를 포함해 8자 이상 입력하세요.</p>
<!-- 안내 예시: "비밀번호, 편집, 필수, 보호됨, 영문, 숫자를 포함해 8자 이상 입력하세요." -->- 형식을 사용자에게 맡기기보다, 하이픈 유무·공백처럼 기계가 처리할 수 있는 차이는 서버에서 정리하는 것이 가장 좋습니다.
type="email",type="tel",inputmode="numeric"을 쓰면 모바일에서 알맞은 키보드가 뜹니다.- 입력 중 자동으로 다음 필드로 포커스를 옮기거나, 값을 고르는 즉시 페이지를 이동하지 않습니다(WCAG 3.2.2 입력 시).
오류 표시와 연결
오류가 생기면 어떤 필드가, 무엇이 잘못되었는지 텍스트로 알려야 하고(WCAG 3.3.1 오류 식별), 가능하면 고치는 방법도 제안합니다(WCAG 3.3.3 오류 수정 제안). 빨간 테두리만으로는 부족합니다.
- 오류가 확인된 필드에
aria-invalid="true"를 줍니다. 화면낭독기는 “잘못됨” 또는 “유효하지 않은 입력”처럼 알립니다. 검사 전(빈 폼을 처음 열었을 때)에는 주지 않고, 오류가 고쳐지면 제거하거나false로 바꿉니다. - 오류 메시지를 필드 가까이에 보이게 표시하고,
aria-describedby로 연결합니다. 이미 형식 안내가 연결되어 있으면 ID를 공백으로 함께 나열합니다. aria-errormessage도 오류 메시지를 연결하는 속성입니다.aria-invalid="true"일 때만 적용되도록 설계되었지만, 화면낭독기 지원이aria-describedby보다 고르지 않습니다. 현재는aria-describedby를 기본으로 쓰는 것이 안전합니다.
<label for="em">이메일</label>
<input type="email" id="em" required autocomplete="email"
aria-invalid="true" aria-describedby="em-hint em-err">
<p id="em-hint">예: [email protected]</p>
<p id="em-err" class="error">이메일 형식이 올바르지 않습니다. @ 뒤에 도메인을 입력하세요.</p><input type="email" id="em2" aria-invalid="true" aria-errormessage="em2-err">
<p id="em2-err">이메일 형식이 올바르지 않습니다.</p>
<!-- aria-invalid가 false이면 aria-errormessage는 무시됨 -->입력할 때마다 오류를 띄우지 마세요
글자를 하나 칠 때마다 “형식이 올바르지 않습니다”를 alert로 알리면 사용자가 입력을 마치기도 전에 계속 방해받습니다. 필드를 벗어날 때(blur)나 제출할 때 검사하고, 오류가 생긴 필드를 고치는 중에는 고쳐진 순간 오류를 지워 주는 방식이 일반적입니다.
autocomplete로 입력 목적 알리기
WCAG 1.3.5 입력 목적 식별(수준 AA)은 이름·이메일·전화번호·주소처럼 사용자 본인에 관한 정보를 받는 필드에 그 목적을 프로그램으로 알 수 있게 하라고 요구합니다. HTML autocomplete 속성에 정해진 토큰을 쓰면 브라우저가 저장된 값을 자동으로 채워 주고, 인지장애·운동장애가 있는 사용자의 입력 부담이 크게 줄어듭니다.
| 필드 | 토큰 |
|---|---|
| 이름(전체) | name |
| 이메일 | email |
| 전화번호 | tel |
| 아이디 | username |
| 현재 비밀번호 / 새 비밀번호 | current-password / new-password |
| 인증번호(OTP) | one-time-code |
| 우편번호 / 주소 | postal-code / street-address, address-line1, address-line2 |
| 생년월일 | bday (또는 bday-year, bday-month, bday-day) |
| 회사명 | organization |
| 카드 번호 | cc-number |
autocomplete="off"로 자동 완성을 막지 않습니다. 비밀번호 관리자를 막는 것은 WCAG 3.3.8 접근 가능한 인증에도 불리합니다.- 배송지에 이미 입력한 주소를 결제지에 다시 입력하게 하지 말고 “배송지와 같음” 선택지를 제공합니다(WCAG 3.3.7 중복 입력).
오류 요약과 포커스 이동
제출했을 때 오류가 있으면, 폼 위쪽에 오류 요약을 보여 주고 그곳으로 포커스를 옮기는 방법이 널리 쓰입니다. 요약에는 오류 개수와 각 오류 필드로 가는 링크를 넣습니다. 오류가 한두 개인 짧은 폼이라면 첫 번째 오류 필드로 바로 포커스를 옮기는 방법도 좋습니다.
- 요약 컨테이너에
tabindex="-1"을 주어 스크립트로 포커스를 받을 수 있게 하고, 제목(“입력 내용을 확인해 주세요. 오류 2개”)을 둡니다. - 포커스를 옮기면 화면낭독기가 요약을 읽으므로, 같은 내용을
alert로 또 알릴 필요는 없습니다. 둘을 함께 쓰면 두 번 읽힙니다. - 링크를 누르면 해당 필드로 포커스를 옮깁니다.
- 페이지를 새로 불러오는 서버 검사라면
<title>을 “오류 2개 - 회원가입”처럼 바꿔 첫 안내에서 오류를 알 수 있게 합니다. - 제출 버튼을 비활성화해 두는 방식은 사용자가 무엇이 부족한지 알기 어렵게 합니다. 버튼은 누를 수 있게 두고, 눌렀을 때 오류를 안내합니다.
실시간 코드
아무것도 입력하지 않고 “가입하기”를 누르면 오류 요약으로 포커스가 이동합니다. 요약의 링크를 누르면 해당 필드로 이동하며, 실시간 코드에서 aria-invalid와 aria-describedby가 바뀌는 것을 볼 수 있습니다. 전체 패턴은 폼 유효성 검사 패턴을 참고하세요.
점검 체크리스트
- 모든 입력 필드에 보이는 레이블이 있고,
<label>등으로 연결되어 있습니다. - 라디오·체크박스 묶음은
<fieldset>/<legend>또는role="group"과 이름으로 묶었습니다. - 필수 항목은 텍스트(또는 설명된 기호)로 보이고,
required/aria-required로도 알립니다. - 형식 안내는 입력 전에 보이고
aria-describedby로 연결되어 있습니다. - 오류는 텍스트로 표시되고,
aria-invalid와 메시지 연결이 실제 상태와 일치합니다. - 사용자 정보 필드에 알맞은
autocomplete토큰이 있습니다. - 제출 후 오류가 있으면 포커스가 오류 요약이나 첫 오류 필드로 이동하고, 성공하면 결과가 안내됩니다.
- 키보드만으로 모든 필드를 입력하고 제출할 수 있습니다.