도입 스토리
"상품 카드 목록이에요." 김개발이 화면을 보여주었습니다.
스크린리더로 카드를 탐색하니 이런 순서로 읽혔습니다.
"자세히 보기 링크. 자세히 보기 링크. 자세히 보기 링크."
모든 카드의 링크가 "자세히 보기"라는 같은 이름이었습니다. 스크린리더 사용자가 링크 목록을 열면 "자세히 보기"가 12개 나열될 뿐, 어느 상품의 링크인지 알 수 없었습니다.
"각 카드의 링크 텍스트에 상품명을 포함시켜야 해요." 박멘토가 말했습니다.
핵심 개념 설명
카드 컴포넌트 접근성
<!-- 상품 카드 목록 --><ul class="product-list" aria-label="추천 상품 목록"> <li class="product-card"> <a href="/products/laptop-pro" class="card-link"> <img src="laptop.jpg" alt="Laptop Pro 15인치 — 메탈 그레이" loading="lazy" > <div class="card-body"> <h3 class="card-title">Laptop Pro</h3> <p class="card-price">1,500,000원</p> <p class="card-desc">M3 칩셋, 16GB 메모리, 256GB SSD</p> </div> </a> <!-- 별도 버튼은 카드 링크 밖에 --> <button type="button" aria-label="Laptop Pro 찜하기" class="wishlist-btn" > ♡ </button> </li> <li class="product-card"> <!-- 다음 카드 --> </li></ul>
카드 전체를 하나의 <a>로 감싸면 링크 텍스트에 이미지 alt, 제목, 가격이 모두 포함되어 스크린리더가 상세히 읽어줍니다.
"자세히 보기" 링크 개선 패턴
카드 안에 별도 "자세히 보기" 버튼이 필요할 때 두 가지 방법이 있습니다.
방법 1 — sr-only로 상세 텍스트 추가:
<a href="/products/laptop-pro"> 자세히 보기 <span class="sr-only">— Laptop Pro</span></a>
스크린리더: "자세히 보기 — Laptop Pro 링크"
방법 2 — aria-label로 전체 이름 지정:
<a href="/products/laptop-pro" aria-label="Laptop Pro 자세히 보기"> 자세히 보기</a>
방법 3 — 카드 전체를 링크로:
<!-- 카드 제목 링크만 사용 (가장 깔끔) --><li class="product-card"> <img src="laptop.jpg" alt=""> <h3> <a href="/products/laptop-pro">Laptop Pro</a> </h3> <p>1,500,000원</p></li>
무한 스크롤과 페이지네이션
페이지네이션 접근성:
<nav aria-label="페이지 이동"> <ul class="pagination"> <li> <a href="?page=1" aria-label="이전 페이지">‹</a> </li> <li> <a href="?page=1">1</a> </li> <li> <a href="?page=2" aria-current="page" aria-label="현재 페이지, 2">2</a> </li> <li> <a href="?page=3">3</a> </li> <li> <a href="?page=3" aria-label="다음 페이지">›</a> </li> </ul></nav>
무한 스크롤 접근성:
무한 스크롤은 키보드 사용자가 새 콘텐츠가 로드된 것을 알 수 없습니다.
<!-- 더 보기 버튼 방식 (무한 스크롤 대안) --><div role="feed" aria-busy="false"> <article><!-- 카드 1 --></article> <article><!-- 카드 2 --></article> <!-- ... --></div><button type="button" id="load-more" aria-controls="product-feed" onclick="loadMore()"> 상품 더 보기 (12개 중 6개 표시 중)</button>
async function loadMore() { const btn = document.getElementById('load-more'); const feed = document.getElementById('product-feed'); btn.disabled = true; btn.textContent = '로딩 중...'; feed.setAttribute('aria-busy', 'true'); const newItems = await fetchMoreProducts(); appendItems(newItems); feed.setAttribute('aria-busy', 'false'); btn.disabled = false; btn.textContent = `상품 더 보기 (${total}개 중 ${shown}개 표시 중)`; // 첫 번째 새 항목으로 포커스 이동 (선택적) const firstNew = feed.querySelector('.new-item'); firstNew?.focus();}
선택 가능한 카드
카드를 선택할 수 있는 경우 (체크박스 카드):
<!-- 카드 선택: 체크박스 패턴 --><ul aria-label="비교할 상품 선택 (최대 3개)"> <li> <label class="product-card-label"> <input type="checkbox" name="compare" value="laptop-pro" aria-describedby="laptop-info" > <span class="card-content"> <img src="laptop.jpg" alt="Laptop Pro"> <span id="laptop-info"> <span class="card-title">Laptop Pro</span> <span class="card-price">1,500,000원</span> </span> </span> </label> </li></ul>
단계별 실습
따라하기: 카드 링크 텍스트 감사
// 카드 내 링크 텍스트 확인document.querySelectorAll('a').forEach(a => { const accessibleName = a.getAttribute('aria-label') || a.textContent.trim(); if (accessibleName.length < 5) { console.warn('너무 짧은 링크 텍스트:', accessibleName, a.href); }});
변형하기: 뉴스 카드 구조 개선
<!-- 수정 전 --><div class="news-card"> <img src="news.jpg"> <h3>개발자를 위한 접근성 가이드</h3> <p>2026.01.15 | 기술 뉴스</p> <a href="/news/1">읽기</a></div><!-- 수정 후 --><article class="news-card"> <a href="/news/1" class="card-link" tabindex="-1" aria-hidden="true"> <img src="news.jpg" alt="" loading="lazy" > </a> <h3> <a href="/news/1">개발자를 위한 접근성 가이드</a> </h3> <p> <time datetime="2026-01-15">2026.01.15</time> | 기술 뉴스 </p></article>
정리와 확인
핵심 내용 요약
- 카드 링크: 상품명/제목이 포함된 구체적인 링크 텍스트
- "자세히 보기":
sr-only또는aria-label로 대상 명시 - 페이지네이션:
aria-current="page"+<nav aria-label> - 무한 스크롤: "더 보기" 버튼 +
aria-busy+ 새 항목 안내 - 장식 이미지: 카드 이미지에
alt=""(제목 링크가 충분한 정보 제공)
확인 문제
문제 1. 카드 목록에서 이미지에 alt=""를 사용하는 경우는?
카드 제목 링크가 이미 충분한 정보를 제공할 때입니다.
제목이 "Laptop Pro"라면 이미지 설명이 중복되어 불필요합니다.
단, 이미지에 제목에 없는 추가 정보가 있다면 alt에 작성합니다.
문제 2. 무한 스크롤보다 "더 보기" 버튼 방식이 접근성에 유리한 이유는?
스크롤 이벤트로만 새 콘텐츠가 로드되면 키보드 사용자가 로드 시점을 알 수 없습니다.
"더 보기" 버튼은 사용자가 직접 제어하고, aria-busy로 로딩 상태를 알릴 수 있습니다.
다음 챕터에서는 폼 컴포넌트의 고급 패턴을 다룹니다. 날짜 선택기, 파일 업로드, 스텝 폼 등 복잡한 입력 인터페이스의 접근성을 알아봅니다.