주요 규칙 소개
JSX 환경에서 eslint-plugin-jsx-a11y를 사용하면, 접근성 문제를 사전에 발견하고 개선할 수 있어요. 이 문서에서는 주요 규칙과 해결 방법을 소개해요.
1. 설정 가이드
설치 방법
yarn add -D eslint-plugin-jsx-a11y
적용 방법
.eslintrc
또는 eslint.config.js
에 아래와 같이 추가해 주세요.
2. 주요 규칙 소개 및 해결 방법
alt-text
<img />
에는 반드시 대체 텍스트(alt)가 필요해요. alt
속성은 정보를 전달하지 않는 이미지라도 빈값이라도 꼭 있어야 하며, 이미지의 목적과 맥락에 맞게 작성해야 해요.
링크에 이미지만 있을 때
❌ 잘못된 예시
<a href="/home">
<img src="home.svg" />
</a>
✅ 올바른 예시
<a href="/home">
<img src="home.svg" alt="홈" />
</a>
정보를 전달하지 않는 이미지
❌ 잘못된 예시
<img src="divider.png" alt="구분선" />
✅ 올바른 예시
<img src="divider.png" alt="" />
텍스트와 함께 있는 아이콘
❌ 잘못된 예시
<button>
<img src="trash-icon.svg" alt="삭제 아이콘" />
삭제
</button>
✅ 올바른 예시
<button>
<img src="trash-icon.svg" alt="" />
삭제
</button>
control-has-associated-label
인터랙티브 요소(입력 필드, 버튼, 선택 상자 등)에는 반드시 사용자에게 그 목적을 명확히 알려주는 이름이 필요해요. 이름이 없거나 불명확한 요소는 스크린 리더 사용자나 음성 지원 사용자에게 큰 불편을 줄 수 있어요. 자세한 내용은 인터랙티브 요소에 이름 넣기 문서에서 확인해 보세요.
eslint-plugin-jsx-ally
의 recommended 룰에 기본적으로 비활성화되어 있으니, 다음과 같이 rules
에 직접 추가해서 활성화 해줘야 해요.
아이콘 버튼에 레이블이 없는 경우
❌ 잘못된 예시
<button>
<img src="close.svg" alt="" />
</button>
✅ 올바른 예시
<button aria-label="닫기">
<img src="close.svg" alt="" />
</button>
// or
<button>
<img src="close.svg" alt="닫기" />
</button>
no-noninteractive-element-interactions
인터랙티브 요소가 아닌 (<div>
, <span>
등)에 클릭 이벤트 핸들러를 추가할 때는 반드시 role
속성 등으로 상호작용 요소임을 명시해야 해요.
인터랙티브 요소 목록
요소 | 조건 |
---|---|
<a> | - |
<audio> | controls 속성이 있는 경우 |
<button> | - |
<details> | - |
<embed> | - |
<iframe> | - |
<img> | usemap 속성이 있는 경우 |
<input> | type 속성이 Hidden state가 아닌 경우 |
<keygen> | - |
<label> | - |
<menu> | type 속성이 toolbar state인 경우 |
<object> | usemap 속성이 있는 경우 |
<select> | - |
<textarea> | - |
<video> | controls 속성이 있는 경우 |
왜 role
속성이 없는 비상호작용 요소에 클릭 이벤트 핸들러를 추가할 수 없나요?
비상호작용 요소에 클릭 이벤트 핸들러를 추가하면, 스크린 리더 등 보조기기가 해당 요소를 인식하지 못해 스크린 리더, 키보드 사용자 등에게 혼란을 줄 수 있어요.
❌ 잘못된 예시
<div onClick={handleClick}>클릭</div>
✅ 올바른 예시
<div role="button" tabIndex={0} onClick={handleClick}>
클릭
</div>
no-noninteractive-element-to-interactive-role
<main>
, <area>
, <h1>
, <h2>
, <img>
, <li>
, <ul>
, <ol>
등 의미 있는 컨테이너 요소에는 button
, link
와 같은 상호작용 역할(interactive role)을 부여하면 안 돼요. 의미에 맞는 태그를 사용해야 해요.
❌ 잘못된 예시
<main role="button" onClick={handleClick}>저장</main>
<ul role="button" onClick={handleClick}>리스트</ul>
<img role="button" onClick={handleClick} src="foo.png" />
✅ 올바른 예시
<button onClick={handleClick}>저장</button>
<a href="/list">리스트</a>
no-noninteractive-tabindex
비상호작용 요소에 tabIndex
를 부여하면 경고가 발생해요. tabIndex
는 상호작용 요소에만 사용해야 해요. 불필요하면 제거하거나 적절한 상호작용 이벤트를 추가해 줘야 해요.
상호작용 요소에만 tabIndex를 부여해야하는 이유
tabIndex
는 키보드 사용자가 Tab 키를 눌러 요소들 사이를 이동할 때 사용되는 속성이에요. 비상호작용 요소에 tabIndex
를 부여하면 다음과 같은 문제가 발생해요.
- 스크린 리더 사용자가 해당 요소가 상호작용 가능하다고 오해할 수 있어요
- 키보드 사용자가 예상치 못한 요소에 포커스할 수 있어요
- DOM의 자연스러운 포커스 순서가 깨질 수 있어요
따라서 tabIndex
는 <button>
, <a>
, <input>
혹은 적절한 role
속성을 부여한 실제로 상호작용이 가능한 요소에만 사용해야 해요.
❌ 잘못된 예시
<div tabIndex={0}>텍스트</div>
✅ 올바른 예시
<span>텍스트</span>
// 또는
<div tabIndex={0} role="button">버튼</div>
tabindex-no-positive
tabIndex
에 1 이상의 값을 쓰면 DOM 순서와 다르게 포커스가 이동해서 동작을 예측하기 어려워져요. 0
이나 -1
만 사용해야 해요.
❌ 잘못된 예시
<button tabIndex={2}>확인</button>
✅ 올바른 예시
<button tabIndex={0}>확인</button>
더 많은 규칙과 예시는 공식 문서를 참고해 주세요: eslint-plugin-jsx-a11y