← 홈페이지 5강 목록으로
♿
EPISODE 01
WCAG 2.1 · 보조기술 · Lighthouse · 랜드마크 · role · aria-* · 키보드 내비게이션 · skip link · label · 에러 메시지 · 색상 대비 · :focus-visible · alt 작성법 · WebVTT 자막 · 색맹 배색 · prefers-reduced-motion

웹 접근성 개념 · 시맨틱과 ARIA · 폼 접근성 · 이미지·미디어 접근성

웹 접근성의 정의와 한국 법적 의무, 장애 유형별 보조기술, WCAG 2.1의 4원칙(인식/운용/이해/견고), 그리고 Lighthouse·axe DevTools·키보드 테스트로 측정하는 방법을 익힙니다. 시맨틱 태그가 만드는 랜드마크, ARIA의 황금 규칙("No ARIA > Bad ARIA"), role과 aria-label/labelledby/describedby/expanded/hidden, 그리고 tabindex와 skip navigation을 익힙니다. label과 input 연결 3가지 방식, role="alert"와 aria-describedby로 에러 전달, aria-required/aria-invalid, 색상 대비 4.5:1, 그리고 절대 제거하면 안 되는 포커스 스타일. alt 텍스트 작성 원칙(장식 vs 정보 vs 링크 vs 버튼), 비디오 자막(WebVTT), 색맹을 고려한 배색, 그리고 움직임 민감 사용자를 위한 prefers-reduced-motion까지.

웹 접근성WCAGLighthousesemanticARIA키보드 내비폼labelcolor contrastaltWebVTTcolor blindnessreduced-motion
소요 시간
⏱ 45분 + 60분 + 45분 + 45분
난이도
📊 중급
선수 조건
🎯 git-coop-04
결과물
Lighthouse 접근성 점수 90+ 페이지를 만드는 기준 확보 / 스크린리더와 키보드만으로 완전히 사용 가능한 컴포넌트 / 스크린리더가 정확히 안내하는 회원가입 폼 / 이미지·영상·애니메이션 모두 접근성 준수

이 강의에서 배우는 것

  • 1웹 접근성의 정의와 법적 의무를 안다
  • 2장애 유형별 주요 보조기술을 파악한다
  • 3WCAG 2.1의 4원칙(POUR)과 적합성 수준(A/AA/AAA)을 안다
  • 4Lighthouse / axe DevTools로 접근성을 측정한다
  • 5키보드만으로 페이지를 사용할 수 있는지 확인한다
  • 6시맨틱 태그가 만드는 랜드마크와 스크린리더 안내를 안다
  • 7ARIA의 황금 규칙을 따른다
  • 8role, aria-label/labelledby/describedby를 적재적소에 쓴다
  • 9aria-expanded로 토글 상태를 전달한다
  • 10tabindex와 skip link로 키보드 내비를 개선한다
  • 11label-for-id, 감싸기, aria-label 3가지 연결 방식을 안다
  • 12role="alert"와 aria-describedby로 에러를 전달한다
  • 13aria-required / aria-invalid 를 적절히 토글한다
  • 14WCAG AA 색상 대비(4.5:1)를 만족한다
  • 15:focus-visible로 포커스 스타일을 안전하게 유지한다
  • 16장식/정보/링크/버튼 이미지의 alt 작성 원칙을 안다
  • 17<track>으로 비디오 자막(WebVTT)을 추가한다
  • 18색맹을 고려해 색상 + 아이콘 + 텍스트로 정보를 전달한다
  • 19@media (prefers-reduced-motion) 으로 움직임을 제어한다

웹 접근성 개념

웹 접근성의 정의와 한국 법적 의무, 장애 유형별 보조기술, WCAG 2.1의 4원칙(인식/운용/이해/견고), 그리고 Lighthouse·axe DevTools·키보드 테스트로 측정하는 방법을 익힙니다.

1. 웹 접근성이란

장애가 있는 사람·고령자 등 모든 사람이 웹 콘텐츠에 접근하고 사용할 수 있도록 보장하는 것.

ℹ️

“웹의 힘은 보편성에 있다. 장애에 관계없이 모든 사람이 접근할 수 있는 것이 웹의 핵심이다.” — 팀 버너스리

  • 법적 의무: 공공기관·일정 규모 이상 민간 웹사이트 (장애인차별금지법 제21조)
  • 한국형 KWCAG 2.1 준수 기준
  • 더 많은 사용자: 국내 장애인 약 260만명
  • SEO 향상 + UX 향상 (비장애인에게도 편의성 ↑)

2. 장애 유형별 보조기술

유형보조기술접근 방식
시각스크린리더 (NVDA, VoiceOver, JAWS)HTML 구조와 텍스트 대안
저시력화면 확대, 고대비 모드텍스트 크기·색상 대비
청각자막, 수화 영상멀티미디어 대안 콘텐츠
운동키보드만 사용, 스위치 제어마우스 없이 모든 기능
인지다양한 보조 앱단순·명확한 UI
html
<!-- 스크린리더는 이렇게 읽음 -->
<a href="/">홈</a>                  <!-- "홈 링크" -->
<button disabled>제출</button>       <!-- "제출 버튼 비활성화됨" -->
<img src="logo.png" alt="회사 로고">  <!-- "회사 로고 이미지" -->
<img src="deco.png" alt="">          <!-- 장식 이미지는 건너뜀 -->

3. WCAG 2.1 — 4원칙 (POUR)

1) 인식 가능 (Perceivable)

html
<img src="chart.png" alt="2024년 1분기 매출: 1억 2천만원">

<video>
  <track kind="captions" src="subtitles.vtt" srclang="ko">
</video>

<!-- 색상만으로 정보 전달 금지 -->

2) 운용 가능 (Operable)

html
<button onclick="...">제출</button>     <!-- ✓ 키보드 OK -->
<div onclick="...">제출</div>           <!-- ✗ 키보드 불가 -->

3) 이해 가능 (Understandable)

html
<html lang="ko">

<!-- 에러 메시지 구체적으로 -->
<!-- ✗ "입력 오류" -->
<!-- ✓ "이메일 형식이 올바르지 않습니다 (예: user@example.com)" -->

4) 견고 (Robust)

올바른 HTML, 닫는 태그, 정확한 ARIA 사용.

4. 적합성 수준

수준설명
A최소 — 미충족 시 일부 사용자 완전 차단
AA권장 — 대부분 법적 기준이 AA
AAA최고 — 모든 상황에서 완전 충족

5. 측정 도구

Lighthouse (Chrome 내장)

  1. F12 → Lighthouse 탭
  2. Accessibility 체크 → Analyze page load
  3. 점수 및 개선 항목 확인

axe DevTools

  1. Chrome 웹스토어에서 axe DevTools 설치
  2. F12 → axe DevTools 탭 → Scan ALL of my page
  3. 위반 항목과 수정 방법 확인

키보드 테스트

text
Tab        다음 포커스
Shift+Tab  이전 포커스
Enter      링크 활성화, 버튼 클릭
Space      버튼/체크박스 토글
화살표      라디오, 선택 메뉴
Escape     모달/팝업 닫기

시맨틱과 ARIA

시맨틱 태그가 만드는 랜드마크, ARIA의 황금 규칙("No ARIA > Bad ARIA"), role과 aria-label/labelledby/describedby/expanded/hidden, 그리고 tabindex와 skip navigation을 익힙니다.

1. 시맨틱 → 랜드마크

html
<!-- ✗ -->
<div class="header"><div class="nav">...</div></div>

<!-- ✓ -->
<header><nav>...</nav></header>
태그ARIA 역할스크린리더 안내
<header>banner배너 영역
<nav>navigation탐색 영역
<main>main주요 콘텐츠
<aside>complementary보완 영역
<footer>contentinfo콘텐츠 정보
<section>region섹션
<article>article독립적 콘텐츠

2. ARIA 황금 규칙

⚠️

“No ARIA is better than Bad ARIA” — 잘못된 ARIA는 없는 것보다 나쁘다. 시맨틱 HTML로 해결할 수 있다면 ARIA를 쓰지 말 것.

3. role

html
<!-- div를 버튼처럼 (피하는 게 좋음) -->
<div role="button" tabindex="0" onclick="..." onkeydown="...">
  클릭
</div>

<!-- 탭 UI -->
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel1">탭1</button>
  <button role="tab" aria-selected="false" aria-controls="panel2">탭2</button>
</div>
<div role="tabpanel" id="panel1">탭1 내용</div>
<div role="tabpanel" id="panel2" hidden>탭2 내용</div>

4. aria-label / labelledby / describedby

html
<!-- 텍스트 없는 버튼 -->
<button aria-label="메뉴 닫기">
  <svg aria-hidden="true">...</svg>
</button>

<!-- 다른 요소를 레이블로 -->
<h2 id="section-title">최신 상품</h2>
<ul aria-labelledby="section-title">...</ul>

<!-- 모달 제목 -->
<div role="dialog" aria-labelledby="modal-title" aria-modal="true">
  <h2 id="modal-title">주문 확인</h2>
</div>

<!-- 추가 설명 -->
<input type="password" id="pw" aria-describedby="pw-hint">
<p id="pw-hint">8자 이상, 대소문자+숫자+특수문자</p>

5. aria-expanded / aria-hidden

html
<button aria-expanded="false" aria-controls="menu" onclick="toggleMenu()">
  메뉴 열기
</button>
<ul id="menu" hidden>...</ul>

<script>
function toggleMenu() {
  const btn = document.querySelector('button');
  const menu = document.getElementById('menu');
  const isOpen = btn.getAttribute('aria-expanded') === 'true';
  btn.setAttribute('aria-expanded', !isOpen);
  menu.hidden = isOpen;
}
</script>

<!-- 장식 아이콘은 스크린리더에서 제외 -->
<button>
  <svg aria-hidden="true" focusable="false">...</svg>
  저장
</button>

6. 키보드 내비게이션

html
<!-- tabindex="0": Tab으로 접근 가능 (자연 순서) -->
<div role="button" tabindex="0">클릭 가능</div>

<!-- tabindex="-1": Tab 불가, JS로 focus() 가능 -->
<div id="modal" tabindex="-1">...</div>

<!-- tabindex="1" 이상: 사용 금지! 자연 순서를 깨뜨림 -->

7. Skip Navigation Link

긴 네비게이션을 키보드로 매번 통과하지 않게 하는 "본문 바로가기" 링크.

html
<a href="#main-content" class="skip-link">본문 바로가기</a>
<nav>... (긴 내비게이션) ...</nav>
<main id="main-content">
  <h1 tabindex="-1">페이지 제목</h1>
</main>
css
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  padding: 8px 16px;
  background: #000;
  color: #fff;
  z-index: 9999;
  text-decoration: none;
}
.skip-link:focus { top: 0; }

폼 접근성

label과 input 연결 3가지 방식, role="alert"와 aria-describedby로 에러 전달, aria-required/aria-invalid, 색상 대비 4.5:1, 그리고 절대 제거하면 안 되는 포커스 스타일.

1. label과 input 연결

방법 1: for/id (권장)

html
<label for="username">사용자 이름</label>
<input type="text" id="username" name="username">

방법 2: 감싸기

html
<label>
  사용자 이름
  <input type="text" name="username">
</label>

방법 3: aria-label

html
<input type="search" aria-label="검색어 입력" placeholder="검색어">
⚠️

placeholder는 레이블 대체품이 아닙니다! 입력 시작하면 사라지고 대비도 낮음.

2. 에러 메시지 — role="alert"

html
<!-- 동적 추가 시 스크린리더가 즉시 읽음 -->
<div role="alert">
  이메일 형식이 올바르지 않습니다. (예: user@example.com)
</div>

<label for="email">이메일</label>
<input
  type="email"
  id="email"
  aria-describedby="email-error"
  aria-invalid="true"
  required
>
<p id="email-error" role="alert">
  이메일 형식이 올바르지 않습니다.
</p>

3. aria-required / aria-invalid

html
<input type="text" required aria-required="true">
<input aria-invalid="false">                                       <!-- 통과 전 -->
<input aria-invalid="true" aria-describedby="error-msg">           <!-- 에러 -->
<span id="error-msg">필수 입력 항목입니다.</span>

<!-- 여러 도움말 연결 (공백 구분) -->
<input type="password" id="pw" aria-describedby="pw-hint pw-error">
<p id="pw-hint">8자 이상, 대소문자+숫자+특수문자</p>
<p id="pw-error" role="alert" hidden>비밀번호가 조건을 충족하지 않습니다.</p>

4. 색상만으로 정보 전달 금지

html
<!-- ✗ 색상만 -->
<input style="border-color: red">

<!-- ✓ 색상 + 아이콘 + 텍스트 -->
<div class="field-error">
  <input aria-invalid="true" aria-describedby="email-error">
  <span class="error-icon" aria-hidden="true">⚠</span>
  <p id="email-error" role="alert">이메일 형식이 올바르지 않습니다.</p>
</div>

5. 색상 대비 (Contrast Ratio)

  • 일반 텍스트: 최소 4.5:1
  • 큰 텍스트(18pt 이상): 최소 3:1
  • UI 요소(테두리, 아이콘): 최소 3:1
css
/* ✗ 대비율 2.3:1 */
input { border: 1px solid #cccccc; }
label { color: #aaaaaa; }

/* ✓ 4.5:1 이상 */
input { border: 2px solid #767676; }
label { color: #1a1a1a; }

확인: WebAIM Contrast Checker — https://webaim.org/resources/contrastchecker/

6. 포커스 스타일 — 절대 제거 금지!

css
/* ✗ 절대 금지 */
:focus { outline: none; }

/* ✓ 방법 1: 기본 outline 개선 */
:focus-visible {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
  border-radius: 2px;
}

/* ✓ 방법 2: 마우스는 숨기고 키보드만 표시 */
:focus { outline: none; }
:focus-visible { outline: 3px solid #0066cc; }

이미지·미디어 접근성

alt 텍스트 작성 원칙(장식 vs 정보 vs 링크 vs 버튼), 비디오 자막(WebVTT), 색맹을 고려한 배색, 그리고 움직임 민감 사용자를 위한 prefers-reduced-motion까지.

1. alt 텍스트 작성 원칙

장식용: alt=""

html
<img src="decoration-wave.svg" alt="">
<img src="spacer.gif" alt="">

정보 전달: 구체적인 설명

html
<!-- ✗ -->
<img src="chart.png" alt="차트">
<img src="photo.jpg" alt="사진">

<!-- ✓ -->
<img src="sales-chart.png" alt="2024년 1분기 매출: 1월 800만원, 2월 950만원, 3월 1,200만원. 전분기 대비 50% 성장.">
<img src="profile.jpg" alt="김철수, 개발팀 시니어 엔지니어">

링크 이미지: 목적지 설명

html
<!-- ✗ 이미지 설명 -->
<a href="/home"><img src="logo.png" alt="파란색 로고"></a>

<!-- ✓ 링크의 목적지 -->
<a href="/home"><img src="logo.png" alt="메인 페이지로 이동"></a>

버튼 이미지

html
<!-- ✗ -->
<button><img src="close.svg" alt="X"></button>

<!-- ✓ 동작 설명 -->
<button><img src="close.svg" alt="닫기"></button>

<!-- 또는 -->
<button aria-label="닫기"><img src="close.svg" alt=""></button>

복잡한 이미지

html
<figure>
  <img src="complex-chart.png" alt="월별 판매 추이 차트" aria-describedby="chart-desc">
  <figcaption id="chart-desc">
    2024년 1~6월 판매 데이터. 1월 800, 2월 950, 3월 1200,
    4월 1100, 5월 1350, 6월 1500건. 전반적 상승 추세.
  </figcaption>
</figure>

2. 비디오 자막 (WebVTT)

html
<video controls>
  <source src="lecture.mp4" type="video/mp4">
  <track
    kind="captions"     <!-- captions: 청각 장애인용 (말+음향), subtitles: 외국어 -->
    src="lecture-ko.vtt"
    srclang="ko"
    label="한국어 자막"
    default
  >
</video>

.vtt 파일 형식

text
WEBVTT

00:00:00.000 --> 00:00:03.500
안녕하세요. 오늘은 웹 접근성에 대해 알아봅니다.

00:00:03.500 --> 00:00:07.000
첫 번째 주제는 이미지 alt 텍스트입니다.

00:00:07.000 --> 00:00:10.500
[화면 전환음]
alt 속성은 이미지를 텍스트로 설명합니다.

3. 색맹을 고려한 배색

약 8%(남성 기준)가 색각 이상. 적록 색맹이 가장 흔함.

html
<!-- ✗ 색상만으로 -->
<p style="color: red">오류가 발생했습니다.</p>
<p style="color: green">성공했습니다.</p>

<!-- ✓ 색상 + 아이콘 + 텍스트 -->
<p class="error">
  <span aria-hidden="true">✗</span>
  오류가 발생했습니다.
</p>
<p class="success">
  <span aria-hidden="true">✓</span>
  성공했습니다.
</p>

도구: Color Oracle (https://colororacle.org) — 화면 전체를 색맹 시뮬레이션으로 볼 수 있음.

4. prefers-reduced-motion

전정기관 장애·간질 등으로 움직임에 민감한 사용자를 위해.

css
.spinner { animation: spin 1s linear infinite; }
.slide   { transition: transform 0.3s ease; }

@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
  .slide   { transition: none; }
  video[autoplay] { animation: none; }
}
javascript
const prefersReducedMotion =
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;

if (!prefersReducedMotion) {
  element.classList.add('animate');
}
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.

댓글은 회원만 쓸 수 있습니다.

광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침