웹 지식

웹 접근성 정리

NiceKHJ 2026. 5. 3. 22:42

1. 웹 접근성이란 ?

장애 유무와 관계없이 모든 사용자가 웹을 동등하게 이용할 수 있도록 보장하는 것이다.

 

대상 장애 유형

  • 시각 장애 (전맹, 저시력, 색각 이상)
  • 청각 장애
  • 운동 장애 (마우스 사용 불가 등)
  • 인지 / 학습 장애

2. WCAG 2.1 -- 4대 원칙 (POUR)

 

원칙 설명
Perceivable 인식 가능해야 한다.
Operable 조작 가능해야 한다.
Understandable 이해 가능해야 한다.
Robust  다양한 환경에서 동작

 

원칙별 실무 적용 예시

<!-- Perceivable: 이미지에 대체 텍스트 제공 -->
<img src="sale-banner.png" alt="여름 세일 최대 50% 할인">

<!-- Operable: div에 click만 달면 키보드 접근 불가 → button 태그 사용 -->
❌ <div onclick="doSomething()">클릭</div>
✅ <button type="button" onclick="doSomething()">클릭</button>

<!-- Understandable: 오류 메시지는 구체적으로 안내 -->
❌ <span>입력 오류</span>
✅ <span role="alert">이메일 형식이 올바르지 않습니다 (예: name@email.com)</span>

<!-- Robust: 표준 시맨틱 태그 사용 -->
❌ <div class="header">...</div>
✅ <header>...</header>

 

 

3. 대체 텍스트 (alt)

개념 이미지 정보를 스크린리더 사용자에게 전달한다.

 

유형 처리
의미 있는 이미지 alt="설명"
장식용 이미지 alt=""(빈 값,속성 자체 생략X)
링크 이미지 링크 목적을 설명하는 텍스트
복잡한 이미지 alt + aria-describedby or 본문 내 추가 설명

 

주의

  • alt 속성 자체가 없으면 스크린리더가 파일명을 그대로 읽음
  • 의미 없는 alt (alt="이미지") 금지
<!-- ✅ 의미 있는 이미지 -->
<img src="sale.png" alt="여름 세일 최대 50% 할인">

<!-- ✅ 장식용 이미지 — alt="" 빈 값으로 처리 (속성 생략 X) -->
<img src="divider.png" alt="">

<!-- ✅ 링크 안의 이미지 — 링크 목적을 설명 -->
<a href="/home">
  <img src="logo.png" alt="지마켓 홈으로 이동">
</a>

<!-- ✅ 복잡한 이미지 (차트 등) — aria-describedby로 본문 설명 연결 -->
<img src="chart.png" alt="2024년 분기별 매출 차트" aria-describedby="chart-desc">
<p id="chart-desc">1분기 120억, 2분기 150억, 3분기 180억, 4분기 210억으로 꾸준히 증가</p>

<!-- ❌ alt 속성 없음 — 스크린리더가 파일명을 그대로 읽음 -->
<img src="banner_2024_v3_final.png">

<!-- ❌ 의미 없는 alt -->
<img src="sale.png" alt="이미지">

 

 

4.키보드 접근성

기본 원칙 모든 기능은 키보드로 조작 가능해야된다.

 

주요 키

동작
Tab / Shift+Tab 포커스 이동
Enter / Space 실행
Esc 닫기

 

포커스 규칙

  • DOM 순서 = 시각적 순서
  • 모달이 열리면 포커스를 모달 내부로 이동
  • 모달이 닫히면 트리거(열기 버튼)로 포커스 복원
  • 모달 내부는 포커스 트랩 적용 (Tab이 모달 밖으로 나가지 않게)
  • 단, 일반 페이지 흐름에서 포커스 트랩은 절대 금지 (모달 / 다이얼로그 한정)
/* 마우스 사용 시에만 아웃라인 숨기기 */
:focus:not(:focus-visible) { outline: none; }
:focus-visible { outline: 2px solid #0066cc; outline-offset: 2px; }

# outline: none 전체 적용 금지 - 키보드 사용자가 현재 위치를 알 수 없게 된다.

# display: none / visibility : hidden 은 접근성 텍스트 숨김 용도로 사용 금지 

 

6. wai-aria

원칙 시맨틱 HTML로 해결 가능하면 aria 사용 금지 aria는 보완재

<!-- ❌ div에 role 붙이는 것보다 -->
<div role="button" tabindex="0">확인</div>

<!-- ✅ 네이티브 button 쓰는 게 낫다 -->
<!-- 네이티브 요소는 키보드 접근, Enter/Space 실행이 기본 내장 -->
<button type="button">확인</button>
종류 설명 예시
role 역할 정의 role="dialog"
property 요소의 속성/관계 aria-label
aria-labelledby
state 현재 상태(동적 변경) aria-expanded
aria-selected

 

7. 주요 aria 속성

aria-label vs aria-labelledby

속성 언제 쓰나
aria-label 레이블 텍스트가 화면에 없을 때 (문자열 직접 제공)
aria-labelledby 레이블이 화면에 이미 있을 때 (요소 id 참조)
<!-- aria-label: 화면에 텍스트가 없는 아이콘 버튼 -->
<button aria-label="검색">
  <svg aria-hidden="true">...</svg>
</button>

<!-- aria-labelledby: 모달 제목이 이미 화면에 있을 때 -->
<dialog aria-labelledby="modal-title">
  <h2 id="modal-title">배송지 변경</h2>
  <p>...</p>
</dialog>
<!-- 스크린리더: "배송지 변경 대화상자" -->

<!-- ❌ 텍스트가 이미 있는데 aria-label 중복 사용 -->
<button aria-label="닫기">닫기</button>
<!-- 스크린리더에 따라 "닫기 닫기" 이중으로 읽힐 수 있음 -->

 

aria-expanded

열림 / 닫힘이 있는 컴포넌트 (드롭다운, 아코디언, 탭 등)에 필수다.

 

<!-- 초기값은 반드시 false (닫혀 있는 상태가 기본) -->
<button aria-expanded="false" aria-controls="menu">카테고리</button>
<ul id="menu" hidden>
  <li><a href="/fashion">패션</a></li>
  <li><a href="/beauty">뷰티</a></li>
</ul>
btn.addEventListener('click', () => {
  const isExpanded = btn.getAttribute('aria-expanded') === 'true';
  btn.setAttribute('aria-expanded', String(!isExpanded));
  menu.hidden = isExpanded;
});

 

# 초기값을 true로 쓰는 실수 주의 - 닫혀 있는 상태가 기본이면 반드시 false로 시작한다.

 

aria-hidden

스크린리더에서 해당 요소와 하위 요소 전체를 숨김

<!-- ✅ 장식용 아이콘 -->
<i class="icon-star" aria-hidden="true"></i>

<!-- ✅ 텍스트 옆 보조 아이콘 -->
<span>3개 선택됨</span>
<svg aria-hidden="true" focusable="false">...</svg>

<!-- ❌ 포커스 가능한 요소에 사용 금지 -->
<!-- 키보드로 포커스는 가는데 스크린리더가 읽지 않아 혼란 야기 -->
<button aria-hidden="true">저장</button>

<!-- ❌ aria-hidden 부모 안에 포커스 가능한 자식 포함 금지 -->
<div aria-hidden="true">
  <button>클릭</button> <!-- 이 버튼도 스크린리더에서 사라짐 -->
</div>

 

role="alert" / role="status"

동적으로 변하는 메시지를 스크린리더에 자동으로 알릴 때 사용

role 특징 용도
alert 즉시 인터럽트해서 읽음 오류, 경고
status 자연스럽게 읽음  저장 완료, 로딩 중
html
<!-- 오류 메시지 영역 -->
<div role="alert" id="error-msg"></div>

<!-- 저장 완료 안내 영역 -->
<div role="status" id="status-msg"></div>

js
// JS로 텍스트를 삽입하는 순간 스크린리더가 자동으로 읽음
document.getElementById('error-msg').textContent = '비밀번호가 일치하지 않습니다.';
document.getElementById('status-msg').textContent = '저장되었습니다.';


** role="alert"는 aria-live="assertive"를 내장하고 있어서 따로 선언하지 않아도 된다.

<!-- ❌ 중복 선언 — 불필요 -->
<div role="alert" aria-live="assertive">...</div>

<!-- ✅ 이렇게만 해도 충분 -->
<div role="alert">...</div>

 

# role="alert" 남발 금지 - 사소한 안내 메시지에 쓰면 UX 흐름이 깨진다.

 

8. 폼 접근성

label 연결

<!-- ✅ for-id 연결 -->
<label for="email">이메일</label>
<input type="email" id="email">

<!-- ✅ 감싸는 방식도 가능 -->
<label>
  이메일
  <input type="email">
</label>

<!-- ❌ placeholder만 사용 금지 — 포커스 시 사라져서 인지 부담 큼 -->
<input type="email" placeholder="이메일 입력">

<!-- ✅ placeholder는 힌트 용도로만, label과 함께 사용 -->
<label for="email">이메일</label>
<input type="email" id="email" placeholder="name@email.com">

 

필수 항목 표시

<!-- ✅ * 기호는 장식이므로 aria-hidden, 필수 여부는 aria-required로 전달 -->
<label for="name">이름 <span aria-hidden="true">*</span></label>
<input type="text" id="name" aria-required="true">

 

오류 처리

html
<label for="phone">전화번호</label>
<input
  type="tel"
  id="phone"
  aria-describedby="phone-error"
  aria-invalid="true"
>
<span id="phone-error" role="alert">
  전화번호 형식이 올바르지 않습니다 (예: 010-0000-0000)
</span>

js
// 유효성 검사 후 상태 동적으로 변경
input.setAttribute('aria-invalid', 'true');
errorMsg.textContent = '전화번호 형식이 올바르지 않습니다';

// 올바르게 입력 시 초기화
input.setAttribute('aria-invalid', 'false');
errorMsg.textContent = '';

 

9. 아이콘 / 이미지 버튼

<!-- ✅ SVG + 숨김 텍스트 -->
<button type="button">
  <svg aria-hidden="true" focusable="false">...</svg>
  <span class="for-a11y">장바구니 담기</span>
</button>

<!-- ✅ img 태그 -->
<button type="button">
  <img src="cart.svg" alt="장바구니 담기">
</button>

<!-- ✅ background-image로 처리할 때 -->
<button type="button" aria-label="장바구니 담기">
  <span aria-hidden="true"></span>
</button>

<!-- ❌ 텍스트 대안 없는 아이콘 버튼 — "버튼"만 읽힘 -->
<button type="button">
  <svg>...</svg>
</button>