콘텐츠로 이동

웹접근성

1.1.1. 1.1 대체 텍스트 (Alternative Text)

섹션 제목: “1.1.1. 1.1 대체 텍스트 (Alternative Text)”

개념: 이미지나 비텍스트 콘텐츠를 볼 수 없는 사용자를 위해 텍스트 설명을 제공합니다.

<!-- 1. img 태그에 alt 속성으로 이미지 설명 제공 -->
<img src="cat.jpg" alt="흰색 고양이가 햇빛 아래 낮잠을 자고 있습니다">
<!-- 2. 장식용 이미지는 빈 alt 사용 (스크린리더가 건너뜀) -->
<img src="line.png" alt="">
<!-- 3. 의미있는 배경 이미지는 aria-label로 설명 -->
<div style="background-image: url('logo.png')"
role="img"
aria-label="회사 로고">
</div>
<!-- 4. 아이콘 버튼에 텍스트 설명 제공 -->
<button aria-label="검색">
<img src="search.png" alt="">
</button>

설명:

  • 1번: 의미있는 이미지는 내용을 설명하는 alt 제공
  • 2번: 장식만을 위한 이미지는 alt를 비워둠
  • 3번: CSS 배경 이미지도 role과 aria-label로 설명 가능
  • 4번: 아이콘 버튼은 버튼의 기능을 aria-label로 설명

개념: 텍스트와 배경 사이에 충분한 명도 대비를 제공하여 저시력 사용자도 내용을 읽을 수 있게 합니다.

  • 일반 텍스트: 최소 4.5:1 대비율
  • 큰 텍스트 (18pt 이상): 최소 3:1 대비율
<!-- 1. 좋은 예: 검정 텍스트 + 흰색 배경 (대비율 21:1) -->
<p style="color: #000; background: #fff">
읽기 쉬운 텍스트입니다
</p>
<!-- 2. 나쁜 예: 회색 텍스트 + 흰색 배경 (대비율 2.5:1) -->
<p style="color: #999; background: #fff">
읽기 어려운 텍스트입니다
</p>
<!-- 3. 좋은 예: 진한 파랑 + 흰색 배경 (대비율 8.6:1) -->
<p style="color: #0056b3; background: #fff">
충분한 대비가 있는 텍스트입니다
</p>
<!-- 4. 큰 텍스트는 대비 기준이 낮음 -->
<h1 style="font-size: 24pt; color: #666; background: #fff">
큰 제목 텍스트
</h1>

설명:

  • 1번: 검정과 흰색은 최고의 대비율 제공
  • 2번: 연한 회색은 대비가 부족하여 접근성 기준 미달
  • 3번: 진한 색상 사용으로 충분한 대비 확보
  • 4번: 큰 텍스트는 3:1 대비율만 필요

1.1.3. 1.3 자막 및 수화 (Captions and Sign Language)

섹션 제목: “1.1.3. 1.3 자막 및 수화 (Captions and Sign Language)”

개념: 청각 장애인을 위해 오디오 콘텐츠에 자막을 제공합니다.

<!-- 1. video 태그에 track 요소로 자막 파일 연결 -->
<video controls>
<!-- 2. src 속성에 비디오 파일 경로 지정 -->
<source src="video.mp4" type="video/mp4">
<!-- 3. track 태그로 자막 파일 추가 (WebVTT 형식) -->
<track kind="captions"
src="captions-ko.vtt"
srclang="ko"
label="한국어">
<!-- 4. 영어 자막도 추가 가능 -->
<track kind="captions"
src="captions-en.vtt"
srclang="en"
label="English">
</video>
<!-- 5. 오디오에도 동일하게 적용 -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
<track kind="captions"
src="transcript.vtt"
srclang="ko"
label="한국어 자막">
</audio>

설명:

  • 1번: video 태그에 controls 속성으로 재생 컨트롤 표시
  • 2번: source로 비디오 파일과 MIME 타입 지정
  • 3번: track으로 자막 파일 연결 (kind=“captions”)
  • 4번: 여러 언어 자막 제공 가능
  • 5번: 오디오도 동일한 방식으로 자막 제공

1.1.4. 1.4 콘텐츠 구조화 (Content Structure)

섹션 제목: “1.1.4. 1.4 콘텐츠 구조화 (Content Structure)”

개념: 올바른 HTML 구조로 콘텐츠의 위계와 의미를 명확하게 전달합니다.

<!-- 1. 페이지에는 하나의 h1 제목만 사용 -->
<h1>웹 접근성 가이드</h1>
<!-- 2. 제목은 순서대로 사용 (h1 → h2 → h3) -->
<h2>인식의 용이성</h2>
<!-- 3. h2 다음에 h3 사용 (h4로 건너뛰지 않음) -->
<h3>대체 텍스트</h3>
<p>이미지에 대한 설명입니다.</p>
<!-- 4. 같은 레벨의 제목은 같은 숫자 사용 -->
<h3>색상 대비</h3>
<p>색상 대비에 대한 설명입니다.</p>
<!-- 5. 다음 섹션으로 넘어갈 때 h2 사용 -->
<h2>운용의 용이성</h2>
<!-- 6. 목록은 ul, ol 태그 사용 -->
<ul>
<li>첫 번째 항목</li>
<li>두 번째 항목</li>
</ul>
<!-- 7. 순서가 중요한 목록은 ol 사용 -->
<ol>
<li>로그인하기</li>
<li>상품 선택하기</li>
<li>결제하기</li>
</ol>

설명:

  • 1번: 페이지당 하나의 h1으로 메인 제목 표시
  • 2번: h1 다음에 h2로 하위 섹션 시작
  • 3번: 제목 레벨을 순서대로 사용 (건너뛰지 않음)
  • 4번: 같은 중요도의 내용은 같은 제목 레벨 사용
  • 5번: 새로운 주요 섹션은 h2로 시작
  • 6번: 순서가 없는 목록은 ul 사용
  • 7번: 순서가 중요한 목록은 ol 사용

1.2.1. 2.1 키보드 접근성 (Keyboard Accessibility)

섹션 제목: “1.2.1. 2.1 키보드 접근성 (Keyboard Accessibility)”

개념: 마우스 없이 키보드만으로 모든 기능을 사용할 수 있어야 합니다.

<!-- 1. button 태그는 기본적으로 키보드 접근 가능 -->
<button onclick="save()">저장</button>
<!-- 2. 나쁜 예: div는 키보드로 접근 불가 -->
<div onclick="save()">저장</div>
<!-- 3. 좋은 예: div에 tabindex와 role 추가 -->
<div tabindex="0"
role="button"
onclick="save()"
onkeypress="if(event.key==='Enter') save()">
저장
</div>
<!-- 4. 링크는 기본적으로 Tab키로 이동 가능 -->
<a href="page.html">다음 페이지</a>
<!-- 5. 폼 요소는 모두 키보드 접근 가능 -->
<input type="text" placeholder="이름 입력">
<select>
<option>옵션 1</option>
<option>옵션 2</option>
</select>
<!-- 6. tabindex로 탭 순서 제어 (양수는 피할 것) -->
<input tabindex="1" type="text">
<input tabindex="2" type="text">
<!-- 7. tabindex="-1"은 포커스는 가능하지만 탭 순서에서 제외 -->
<div tabindex="-1" id="msg">알림 메시지</div>
<!-- 8. 모달 창에서는 포커스 트랩 구현 -->
<div role="dialog" aria-modal="true">
<h2>알림</h2>
<p>내용입니다</p>
<button>확인</button>
</div>

설명:

  • 1번: button은 Tab, Enter, Space 키로 자동 작동
  • 2번: div는 기본적으로 포커스 불가능
  • 3번: tabindex=“0”으로 포커스 가능하게 만듦
  • 4번: a 태그는 Tab으로 이동, Enter로 클릭
  • 5번: 모든 input, select 등은 키보드 접근 가능
  • 6번: tabindex 양수는 탭 순서를 복잡하게 만들어 피해야 함
  • 7번: tabindex=“-1”은 JS로만 포커스 가능
  • 8번: 모달에서는 포커스가 모달 내부에만 머물러야 함

1.2.2. 2.2 충분한 시간 제공 (Enough Time)

섹션 제목: “1.2.2. 2.2 충분한 시간 제공 (Enough Time)”

개념: 사용자가 콘텐츠를 읽고 사용할 충분한 시간을 제공합니다.

<!-- 1. 자동으로 넘어가는 슬라이드에 일시정지 버튼 제공 -->
<div id="slide">
<img src="slide1.jpg" alt="첫 번째 슬라이드">
<!-- 2. 일시정지, 재생 버튼 -->
<button onclick="pause()">일시정지</button>
<button onclick="play()">재생</button>
</div>
<script>
// 3. 자동 슬라이드 변수
let timer;
let paused = false;
// 4. 슬라이드 자동 전환 함수
function autoSlide() {
// 5. 일시정지 상태가 아닐 때만 실행
if (!paused) {
// 슬라이드 전환 코드
}
// 6. 5초마다 실행 (충분한 시간 제공)
timer = setTimeout(autoSlide, 5000);
}
// 7. 일시정지 함수
function pause() {
paused = true;
clearTimeout(timer);
}
// 8. 재생 함수
function play() {
paused = false;
autoSlide();
}
</script>
<!-- 9. 세션 타임아웃 경고 -->
<div id="warn" style="display:none">
<!-- 10. 남은 시간 표시 -->
<p>5분 후 자동 로그아웃됩니다.</p>
<!-- 11. 연장 버튼 제공 -->
<button onclick="extend()">시간 연장</button>
</div>
<script>
// 12. 타임아웃 전 경고 표시
setTimeout(function() {
document.getElementById('warn').style.display = 'block';
}, 25 * 60 * 1000); // 25분 후 경고
</script>

설명:

  • 1번: 자동 슬라이드를 포함한 div 영역
  • 2번: 사용자가 슬라이드를 제어할 수 있는 버튼
  • 3번: 타이머와 일시정지 상태를 저장하는 변수
  • 4번: 슬라이드를 자동으로 전환하는 함수
  • 5번: 일시정지 중이 아닐 때만 슬라이드 전환
  • 6번: 5초 간격으로 충분한 읽기 시간 제공
  • 7번: 사용자가 일시정지 버튼을 누르면 타이머 중지
  • 8번: 재생 버튼으로 다시 자동 전환 시작
  • 9번: 세션 만료 경고를 위한 숨겨진 div
  • 10번: 남은 시간을 알려주는 메시지
  • 11번: 세션을 연장할 수 있는 버튼
  • 12번: 타임아웃 5분 전에 경고 표시

1.2.3. 2.3 발작 예방 (Seizure Prevention)

섹션 제목: “1.2.3. 2.3 발작 예방 (Seizure Prevention)”

개념: 깜빡이거나 번쩍이는 콘텐츠로 인한 발작을 예방합니다.

<!-- 1. 나쁜 예: 빠르게 깜빡이는 애니메이션 -->
<div style="animation: blink 0.3s infinite">
깜빡임
</div>
<style>
/* 2. 초당 3회 이상 깜빡임은 위험 */
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
</style>
<!-- 3. 좋은 예: 애니메이션 제어 옵션 제공 -->
<button onclick="stop()">애니메이션 중지</button>
<div id="anim">
움직이는 콘텐츠
</div>
<script>
// 4. 애니메이션 중지 함수
function stop() {
// 5. CSS 애니메이션 제거
document.getElementById('anim').style.animation = 'none';
}
</script>
<!-- 6. prefers-reduced-motion으로 사용자 설정 존중 -->
<style>
/* 7. 기본 애니메이션 */
.box {
transition: transform 0.3s;
}
/* 8. 사용자가 애니메이션 줄이기를 선택한 경우 */
@media (prefers-reduced-motion: reduce) {
.box {
/* 9. 모든 애니메이션과 전환 효과 제거 */
transition: none;
animation: none;
}
}
</style>

설명:

  • 1번: 0.3초마다 깜빡이는 것은 발작을 유발할 수 있음
  • 2번: 초당 3회 이상의 깜빡임은 접근성 위반
  • 3번: 사용자가 애니메이션을 멈출 수 있는 버튼 제공
  • 4번: 버튼 클릭 시 애니메이션 중지하는 함수
  • 5번: CSS의 animation 속성을 none으로 설정
  • 6번: 미디어 쿼리로 사용자 선호도 확인
  • 7번: 일반 사용자를 위한 기본 애니메이션
  • 8번: 애니메이션 줄이기 설정을 한 사용자 감지
  • 9번: 해당 사용자에게는 모든 움직임 제거

개념: 사용자가 콘텐츠를 쉽게 탐색할 수 있도록 명확한 네비게이션을 제공합니다.

<!-- 1. 본문 바로가기 링크 (페이지 최상단) -->
<a href="#main" class="skip">본문 바로가기</a>
<!-- 2. 메인 네비게이션을 nav 태그로 감싸기 -->
<nav aria-label="주 메뉴">
<!-- 3. 목록으로 메뉴 구조화 -->
<ul>
<li><a href="/"></a></li>
<li><a href="/about">소개</a></li>
<li><a href="/contact">연락처</a></li>
</ul>
</nav>
<!-- 4. 현재 페이지 표시 -->
<nav aria-label="주 메뉴">
<ul>
<li><a href="/"></a></li>
<!-- 5. aria-current로 현재 페이지 표시 -->
<li><a href="/about" aria-current="page">소개</a></li>
<li><a href="/contact">연락처</a></li>
</ul>
</nav>
<!-- 6. 빵 부스러기(breadcrumb) 네비게이션 -->
<nav aria-label="빵부스러기">
<!-- 7. 순서가 있는 목록 사용 -->
<ol>
<li><a href="/"></a></li>
<li><a href="/products">상품</a></li>
<!-- 8. 현재 위치는 링크 없이 표시 -->
<li aria-current="page">노트북</li>
</ol>
</nav>
<!-- 9. 본문 영역은 main 태그로 표시 -->
<main id="main">
<h1>페이지 제목</h1>
<p>본문 내용입니다.</p>
</main>
<!-- 10. 페이지 내 점프 링크 -->
<nav aria-label="목차">
<ul>
<!-- 11. 각 섹션의 id로 이동 -->
<li><a href="#sec1">섹션 1</a></li>
<li><a href="#sec2">섹션 2</a></li>
<li><a href="#sec3">섹션 3</a></li>
</ul>
</nav>
<!-- 12. 각 섹션에 id 부여 -->
<section id="sec1">
<h2>섹션 1</h2>
</section>
<section id="sec2">
<h2>섹션 2</h2>
</section>

설명:

  • 1번: 페이지 최상단에 본문으로 바로 가는 링크 제공
  • 2번: nav 태그와 aria-label로 네비게이션 영역 명확히 구분
  • 3번: 메뉴는 ul, li로 구조화하여 개수 파악 용이
  • 4번: 네비게이션 영역 정의
  • 5번: aria-current=“page”로 현재 페이지 표시
  • 6번: 빵부스러기는 사용자의 현재 위치 표시
  • 7번: 순서가 중요하므로 ol 사용
  • 8번: 현재 위치는 링크가 아닌 텍스트로 표시
  • 9번: main 태그로 주요 콘텐츠 영역 표시
  • 10번: 페이지 내 섹션으로 이동하는 목차
  • 11번: href=“#id”로 같은 페이지 내 이동
  • 12번: 각 섹션에 고유한 id 값 부여

1.3. 이해의 용이성 (Understandability)

섹션 제목: “1.3. 이해의 용이성 (Understandability)”

1.3.1. 3.1 읽기 쉬운 텍스트 (Readable Text)

섹션 제목: “1.3.1. 3.1 읽기 쉬운 텍스트 (Readable Text)”

개념: 명확하고 이해하기 쉬운 언어를 사용합니다.

<!-- 1. 페이지 언어 지정 (한국어) -->
<html lang="ko">
<!-- 2. 특정 부분만 다른 언어일 경우 -->
<p>
<!-- 3. 한국어 문장 -->
안녕하세요.
<!-- 4. 영어 부분만 lang 속성 지정 -->
<span lang="en">Hello</span>입니다.
</p>
<!-- 5. 약어나 줄임말에 설명 제공 -->
<p>
<!-- 6. abbr 태그로 약어 표시 -->
<abbr title="하이퍼텍스트 마크업 언어">HTML</abbr>
웹 페이지를 만드는 언어입니다.
</p>
<!-- 7. 복잡한 단어에 설명 추가 -->
<p>
<!-- 8. dfn 태그로 정의 표시 -->
<dfn title="웹 페이지를 모두가 사용할 수 있게 만드는 것">
웹 접근성
</dfn>은 중요합니다.
</p>
<!-- 9. 발음이 어려운 단어에 루비 주석 -->
<ruby>
<!-- 10. 한자 표시 -->
漢字
<!-- 11. 읽는 방법 표시 -->
<rt>한자</rt>
</ruby>

설명:

  • 1번: html 태그에 lang 속성으로 페이지 언어 지정
  • 2번: 문장 안에 다른 언어가 섞여 있는 경우
  • 3번: 기본 언어는 한국어로 설정됨
  • 4번: 영어 부분에만 lang=“en” 추가
  • 5번: 약어는 의미를 모를 수 있음
  • 6번: abbr 태그의 title로 전체 명칭 제공
  • 7번: 어려운 용어에 설명 필요
  • 8번: dfn 태그의 title로 용어 정의
  • 9번: 한자나 어려운 글자에 읽는 법 표시
  • 10번: 원래 글자 (한자)
  • 11번: rt 태그로 읽는 방법 (한글) 표시

1.3.2. 3.2 예측 가능성 (Predictability)

섹션 제목: “1.3.2. 3.2 예측 가능성 (Predictability)”

개념: 웹 페이지가 예측 가능한 방식으로 작동해야 합니다.

<!-- 1. 나쁜 예: 포커스만으로 팝업 열림 -->
<input type="text"
onfocus="openPopup()">
<!-- 2. 좋은 예: 명시적 클릭으로 팝업 열림 -->
<input type="text" id="inp">
<button onclick="openPopup()">도움말 보기</button>
<!-- 3. 나쁜 예: 선택만으로 페이지 이동 -->
<select onchange="location.href=this.value">
<option value="page1.html">페이지 1</option>
<option value="page2.html">페이지 2</option>
</select>
<!-- 4. 좋은 예: 버튼으로 이동 확인 -->
<select id="sel">
<option value="page1.html">페이지 1</option>
<option value="page2.html">페이지 2</option>
</select>
<!-- 5. 이동 버튼 별도 제공 -->
<button onclick="go()">이동</button>
<script>
// 6. 사용자가 버튼을 눌러야 이동
function go() {
const sel = document.getElementById('sel');
location.href = sel.value;
}
</script>
<!-- 7. 새 창 열림을 사전에 알림 -->
<a href="page.html"
target="_blank">
<!-- 8. 새 창에서 열림을 명시 -->
외부 사이트 (새 창)
</a>
<!-- 9. 또는 aria-label로 설명 -->
<a href="page.html"
target="_blank"
aria-label="외부 사이트, 새 창에서 열림">
외부 사이트
</a>

설명:

  • 1번: 포커스만으로 팝업이 열리면 예측 불가
  • 2번: 별도 버튼으로 사용자가 의도적으로 실행
  • 3번: select 변경만으로 페이지 이동은 혼란스러움
  • 4번: select는 선택만 함
  • 5번: 이동 버튼을 따로 제공하여 명확한 의도 표현
  • 6번: 버튼 클릭 시에만 페이지 이동 실행
  • 7번: 새 창으로 열릴 때 텍스트로 명시
  • 8번: 괄호 안에 “새 창” 표시
  • 9번: 또는 aria-label로 스크린리더 사용자에게 알림

1.3.3. 3.3 입력 지원 (Input Assistance)

섹션 제목: “1.3.3. 3.3 입력 지원 (Input Assistance)”

개념: 사용자가 오류를 방지하고 수정할 수 있도록 도와줍니다.

<!-- 1. 폼에 명확한 레이블 제공 -->
<form>
<!-- 2. label과 input을 for-id로 연결 -->
<label for="name">이름</label>
<input type="text" id="name" required>
<!-- 3. 필수 입력 표시와 설명 -->
<label for="email">
이메일
<!-- 4. 필수 표시 -->
<span aria-label="필수">*</span>
</label>
<!-- 5. 입력 형식 안내 -->
<input type="email"
id="email"
placeholder="example@email.com"
required
aria-describedby="tip">
<!-- 6. 추가 설명 제공 -->
<small id="tip">이메일 형식으로 입력해주세요</small>
<!-- 7. 오류 메시지 영역 -->
<div id="err"
role="alert"
style="display:none; color:red">
</div>
<button type="submit">제출</button>
</form>
<script>
// 8. 폼 제출 시 유효성 검사
document.querySelector('form').addEventListener('submit', function(e) {
// 9. 기본 제출 동작 막기
e.preventDefault();
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const err = document.getElementById('err');
// 10. 이름이 비어있는지 확인
if (!name) {
// 11. 오류 메시지 표시
err.textContent = '이름을 입력해주세요';
err.style.display = 'block';
// 12. 해당 필드로 포커스 이동
document.getElementById('name').focus();
return;
}
// 13. 이메일 형식 확인
if (!email.includes('@')) {
err.textContent = '올바른 이메일 형식이 아닙니다';
err.style.display = 'block';
document.getElementById('email').focus();
return;
}
// 14. 모두 정상이면 제출
alert('제출 완료');
});
</script>
<!-- 15. 비밀번호 입력 시 조건 표시 -->
<label for="pw">비밀번호</label>
<input type="password"
id="pw"
aria-describedby="pwrule">
<!-- 16. 비밀번호 규칙 설명 -->
<ul id="pwrule">
<li>8자 이상</li>
<li>영문, 숫자 포함</li>
</ul>

설명:

  • 1번: 폼 요소들을 form 태그로 감싸기
  • 2번: label의 for와 input의 id를 동일하게 연결
  • 3번: label 안에 필수 표시 포함
  • 4번: 별표(*)로 필수 필드 표시, aria-label로 의미 전달
  • 5번: placeholder로 입력 예시 표시
  • 6번: aria-describedby로 연결된 설명 텍스트
  • 7번: role=“alert”로 오류 메시지 영역 지정
  • 8번: submit 이벤트로 유효성 검사 실행
  • 9번: preventDefault로 바로 제출되지 않게 함
  • 10번: 입력값이 비어있는지 확인
  • 11번: 오류 메시지를 사용자에게 표시
  • 12번: 오류가 있는 필드로 포커스 이동
  • 13번: 이메일에 @가 있는지 간단히 확인
  • 14번: 모든 검사 통과 시 실제 제출 진행
  • 15번: 비밀번호 필드
  • 16번: 비밀번호 규칙을 미리 안내

1.3.4. 3.4 오류 식별 및 제안 (Error Identification)

섹션 제목: “1.3.4. 3.4 오류 식별 및 제안 (Error Identification)”

개념: 오류를 명확하게 표시하고 수정 방법을 제안합니다.

<form id="frm">
<!-- 1. 각 입력 필드에 고유 id 부여 -->
<div>
<label for="user">사용자명</label>
<!-- 2. aria-invalid로 오류 상태 표시 -->
<input type="text"
id="user"
aria-invalid="false"
aria-describedby="user-err">
<!-- 3. 오류 메시지 영역 (처음엔 숨김) -->
<span id="user-err"
role="alert"
style="display:none; color:red">
</span>
</div>
<div>
<label for="age">나이</label>
<input type="number"
id="age"
aria-invalid="false"
aria-describedby="age-err">
<span id="age-err"
role="alert"
style="display:none; color:red">
</span>
</div>
<button type="submit">제출</button>
</form>
<script>
// 4. 폼 제출 이벤트 처리
document.getElementById('frm').addEventListener('submit', function(e) {
// 5. 기본 제출 막기
e.preventDefault();
// 6. 오류 여부 추적
let hasError = false;
// 7. 사용자명 검증
const user = document.getElementById('user');
const userErr = document.getElementById('user-err');
// 8. 빈 값 체크
if (!user.value.trim()) {
// 9. aria-invalid를 true로 변경
user.setAttribute('aria-invalid', 'true');
// 10. 오류 메시지 표시
userErr.textContent = '사용자명을 입력해주세요';
userErr.style.display = 'block';
hasError = true;
} else {
// 11. 정상이면 aria-invalid를 false로
user.setAttribute('aria-invalid', 'false');
userErr.style.display = 'none';
}
// 12. 나이 검증
const age = document.getElementById('age');
const ageErr = document.getElementById('age-err');
// 13. 나이 범위 체크
if (age.value < 1 || age.value > 120) {
age.setAttribute('aria-invalid', 'true');
// 14. 구체적인 수정 방법 제시
ageErr.textContent = '나이는 1~120 사이로 입력해주세요';
ageErr.style.display = 'block';
hasError = true;
} else {
age.setAttribute('aria-invalid', 'false');
ageErr.style.display = 'none';
}
// 15. 오류가 없으면 제출
if (!hasError) {
alert('제출 완료');
} else {
// 16. 오류가 있으면 첫 오류 필드로 포커스
if (user.getAttribute('aria-invalid') === 'true') {
user.focus();
} else {
age.focus();
}
}
});
</script>

설명:

  • 1번: 각 필드에 고유한 id를 부여하여 구분
  • 2번: aria-invalid로 현재 입력값의 오류 여부 표시
  • 3번: role=“alert”로 오류 메시지가 즉시 읽히도록 함
  • 4번: 폼 제출 시 검증 로직 실행
  • 5번: 검증 전에 제출되지 않도록 preventDefault
  • 6번: 오류 발생 여부를 추적하는 변수
  • 7번: 사용자명 필드와 오류 메시지 요소 가져오기
  • 8번: 입력값이 비어있는지 확인 (공백 제거 후)
  • 9번: 오류가 있으면 aria-invalid를 true로 설정
  • 10번: 화면에 오류 메시지 표시
  • 11번: 정상이면 aria-invalid를 false로 돌림
  • 12번: 나이 필드 검증 시작
  • 13번: 나이가 유효한 범위인지 확인
  • 14번: 어떻게 수정해야 하는지 구체적으로 안내
  • 15번: 모든 필드가 정상이면 제출 진행
  • 16번: 오류가 있으면 첫 번째 오류 필드로 포커스 이동

웹 접근성의 세 가지 핵심 원칙:

  1. 인식의 용이성: 모든 사용자가 콘텐츠를 인식할 수 있어야 합니다

    • 대체 텍스트 제공
    • 충분한 색상 대비
    • 자막 제공
    • 명확한 구조
  2. 운용의 용이성: 모든 사용자가 인터페이스를 사용할 수 있어야 합니다

    • 키보드 접근성
    • 충분한 시간 제공
    • 발작 예방
    • 명확한 네비게이션
  3. 이해의 용이성: 콘텐츠와 인터페이스가 이해하기 쉬워야 합니다

    • 읽기 쉬운 텍스트
    • 예측 가능한 동작
    • 입력 지원
    • 오류 식별 및 제안