React로 접근 가능한 폼 구성하기
레이블과 에러 메시지 중심으로 React에서 접근성을 높이는 폼 구성 원칙과 코드 예시를 포함한 실무적 설명
목차
개요
웹 폼은 사용자 인터랙션의 핵심 요소다. 접근성이 떨어지면 일부 사용자는 기능을 온전하게 이용할 수 없다. React 환경에서 접근성(Accessibility)을 고려한 폼을 설계하면 화면 읽기 프로그램 사용자와 키보드 사용자 모두에게 더 나은 경험을 제공한다. 이 글은 레이블 연결, 에러 메시지 전달, ARIA 속성 활용을 중심으로 실무적으로 정리한 내용이다.
기본 원칙
접근 가능한 폼을 만들 때 지켜야 할 기본 원칙은 다음과 같다.
- 각 입력 요소는 명확한 레이블(label)과 연결되어야 한다.
- 오류는 시각적으로만 표시하지 않고 스크린리더에 전달되어야 한다.
- ARIA 속성은 시맨틱 요소를 보완하는 용도로만 사용한다.
- 키보드로 모든 폼 제어를 조작할 수 있어야 한다.
레이블 연결 방법
label과 htmlFor 사용
가장 표준적인 방법은 <label>을 사용해 입력의 id와 연결하는 것이다. 이 방식은 클릭으로 포커스를 옮길 수 있게 하며 스크린리더가 레이블을 읽게 한다.
function AccessibleInput({ id, label, ...props }) {
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} {...props} />
</div>
);
}
시각적 레이블이 필요 없는 경우
레이블을 숨겨야 할 때는 화면에서는 보이지 않지만 보조 기술에는 전달되는 스타일을 적용한다. 단순히 display:none을 쓰면 스크린리더에게도 숨겨진다. 숨김용 CSS 클래스는 접근성 관점에서 검토된 방법을 사용한다.
에러 메시지 전달
사용자 입력 오류는 단순한 빨간색 표시로만 제공해서는 안 된다. 스크린리더 사용자에게도 오류 내용을 명확히 전달해야 한다.
aria-invalid와 aria-describedby
입력에 오류가 생기면 aria-invalid="true"를 설정하고, 에러 텍스트를 가리키는 id를 aria-describedby로 연결한다. 이렇게 하면 스크린리더가 입력과 관련된 오류 문구를 읽게 된다.
function EmailField({ value, onChange, error }) {
const errorId = error ? 'email-error' : undefined;
return (
<div>
<label htmlFor="email">이메일</label>
<input
id="email"
name="email"
value={value}
onChange={onChange}
aria-invalid={!!error}
aria-describedby={errorId}
/>
{error && (
<p id="email-error" role="alert">{error}</p>
)}
</div>
);
}
role="alert"는 동적으로 추가될 때 화면 읽기 프로그램이 즉시 알리도록 도와준다. 단, 과도한 알림은 방해가 될 수 있으므로 중요 정보에만 사용한다.
ARIA 사용 시 주의점
ARIA는 시맨틱한 HTML을 대체하기 위한 수단이 아니다. 가능하면 native HTML 요소를 먼저 사용하고, 부족한 부분을 보완하는 식으로 ARIA를 적용한다. 예를 들어 <input type="email">은 내장 유효성 검사를 제공한다. 이때 aria-describedby를 함께 쓰면 유효성 피드백이 더 명확해진다.
실전 구성 예시
아래 예시는 레이블 연결, 에러 표시, 제출 시 포커스 관리까지 고려한 간단한 React 폼이다.
function SimpleForm() {
const [values, setValues] = React.useState({ email: '' });
const [errors, setErrors] = React.useState({});
function validate() {
const newErrors = {};
if (!values.email) newErrors.email = '이메일은 필수 입력입니다.';
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) newErrors.email = '유효한 이메일 주소가 아님.';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
}
function handleSubmit(e) {
e.preventDefault();
if (!validate()) {
const firstErrorField = document.querySelector('[aria-invalid="true"]');
if (firstErrorField) firstErrorField.focus();
return;
}
// 제출 처리
}
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">이메일</label>
<input
id="email"
name="email"
value={values.email}
onChange={e => setValues({ ...values, email: e.target.value })}
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && <p id="email-error" role="alert">{errors.email}</p>}
</div>
<button type="submit">전송</button>
</form>
);
}
검증과 사용자 경험
검증 피드백은 즉시성과 명확성이 중요하다. 입력 도중 실시간 검증을 제공하면 오류를 조기에 고칠 수 있다. 다만 실시간 피드백은 과도한 알림을 만들지 않도록 설계해야 한다. 또한 폼 제출 후 포커스를 첫 오류 필드로 이동하면 키보드 사용자에게 큰 도움이 된다.
요약
- 레이블은 반드시 입력과 연결한다.
- 오류는 aria-invalid와 aria-describedby로 스크린리더에 전달한다.
- role="alert"는 동적 오류 알림에 유용하다.
- ARIA는 시맨틱 HTML을 보완하는 도구로만 사용한다.
이 원칙을 따르면 React 환경에서 폼의 접근성을 크게 개선할 수 있다. 추가로 자동화된 접근성 검사 도구와 실제 보조 기술 테스트를 병행하면 더 안전한 결과를 얻을 수 있다.