도입 스토리
"버튼 접근성이요?" 김개발이 의아해했습니다. "버튼은 그냥 <button> 쓰면 되는 거 아닌가요?"
"90%는 맞아요." 박멘토가 말했습니다. "그런데 실무에서는 아이콘만 있는 버튼, 비활성 버튼, 토글 버튼, 그리고 <a> vs <button> 선택 문제가 계속 나와요."
박멘토가 화면에서 아이콘 버튼 하나를 가리켰습니다. 스크린리더로 읽었더니 "버튼"이라고만 들렸습니다. 이름이 없었습니다.
"이 버튼이 무엇을 하는 버튼인지 알 방법이 없어요."
핵심 개념 설명
버튼 vs 링크 구분
가장 흔한 실수 중 하나는 버튼과 링크를 혼용하는 것입니다.
| 요소 | 사용 시기 |
|---|---|
<button> |
동작 수행 (저장, 삭제, 제출, 열기/닫기, 토글) |
<a href="..."> |
페이지 이동, URL 이동 |
<!-- 올바른 사용 --><button type="submit">로그인</button><button type="button" onclick="openModal()">상세 보기</button><a href="/signup">회원가입</a><a href="/profile">내 프로필</a><!-- 잘못된 사용 --><a href="#" onclick="openModal()">상세 보기</a> <!-- 이동 없는 링크 --><button onclick="location.href='/signup'">회원가입</button> <!-- URL 이동 버튼 -->
href="#" 링크는 Enter로 활성화되지만 페이지 최상단으로 이동하거나 # URL이 히스토리에 쌓이는 문제가 생깁니다.
아이콘 버튼 이름 제공
아이콘만 있는 버튼은 반드시 텍스트 이름을 제공합니다.
<!-- 방법 1: aria-label (가장 간단) --><button type="button" aria-label="검색"> <svg aria-hidden="true" focusable="false"> <!-- 검색 아이콘 SVG --> </svg></button><!-- 방법 2: sr-only 텍스트 --><button type="button"> <svg aria-hidden="true" focusable="false"><!-- 아이콘 --></svg> <span class="sr-only">삭제</span></button><!-- 방법 3: title (hover 팁도 겸함) --><button type="button" title="공유하기"> <svg aria-hidden="true"><!-- 공유 아이콘 --></svg></button>
SVG에 aria-hidden="true"와 focusable="false"를 설정해 스크린리더와 키보드 탐색에서 제외합니다.
토글 버튼
켜고 끄는 버튼에는 aria-pressed를 사용합니다.
<!-- 북마크 토글 버튼 --><button type="button" id="bookmark-btn" aria-pressed="false" onclick="toggleBookmark(this)"> <svg aria-hidden="true"><!-- 북마크 아이콘 --></svg> <span>북마크</span></button>
function toggleBookmark(btn) { const isPressed = btn.getAttribute('aria-pressed') === 'true'; btn.setAttribute('aria-pressed', !isPressed); // 스크린리더: "북마크 누름" 또는 "북마크 누르지 않음"}
aria-pressed="true": 활성(눌린 상태), aria-pressed="false": 비활성(눌리지 않은 상태).
비활성 버튼
<!-- HTML disabled: 완전히 비활성화 (포커스 불가) --><button type="submit" disabled>저장 (처리 중)</button><!-- aria-disabled: 시각적 비활성, 포커스는 가능 --><button type="button" aria-disabled="true" onclick="handleDisabledClick()"> 제출</button>
aria-disabled="true"는 보조기기에 비활성 상태를 알리면서도 포커스는 유지합니다. 사용자가 포커스했을 때 왜 비활성인지 설명할 기회를 줄 수 있습니다.
function handleDisabledClick() { const btn = document.querySelector('[aria-disabled="true"]'); // 클릭해도 동작하지 않음 if (btn.getAttribute('aria-disabled') === 'true') return; // 실제 동작...}
버튼 그룹과 툴바
버튼이 그룹을 이룰 때 role="toolbar"로 묶습니다.
<div role="toolbar" aria-label="텍스트 서식"> <button type="button" aria-pressed="false" aria-label="굵게">B</button> <button type="button" aria-pressed="false" aria-label="기울임">I</button> <button type="button" aria-pressed="false" aria-label="밑줄">U</button> <button type="button" aria-label="링크 삽입">🔗</button></div>
단계별 실습
따라하기: 아이콘 버튼 감사
페이지의 아이콘 버튼들이 접근 가능한 이름을 가지고 있는지 확인합니다.
// 이름 없는 버튼/링크 찾기document.querySelectorAll('button, a').forEach(el => { const text = el.textContent.trim(); const ariaLabel = el.getAttribute('aria-label'); const ariaLabelledBy = el.getAttribute('aria-labelledby'); const title = el.getAttribute('title'); if (!text && !ariaLabel && !ariaLabelledBy && !title) { console.error('이름 없는 인터랙티브 요소:', el.outerHTML.substring(0, 100)); }});
변형하기: 좋아요 버튼 접근성 개선
<!-- 수정 전: 이름도, 상태도 없음 --><button onclick="toggleLike()">♥ 42</button><!-- 수정 후: 상태와 이름 제공 --><button type="button" aria-pressed="false" aria-label="좋아요 42개, 좋아요 누르기" onclick="toggleLike(this)"> <span aria-hidden="true">♥</span> <span class="count">42</span></button>
function toggleLike(btn) { const isLiked = btn.getAttribute('aria-pressed') === 'true'; const count = parseInt(btn.querySelector('.count').textContent); const newCount = isLiked ? count - 1 : count + 1; btn.setAttribute('aria-pressed', !isLiked); btn.querySelector('.count').textContent = newCount; btn.setAttribute('aria-label', `좋아요 ${newCount}개, ${isLiked ? '좋아요 누르기' : '좋아요 취소'}`);}
정리와 확인
핵심 내용 요약
- 버튼 vs 링크: 동작은
<button>, 이동은<a href>— 목적에 맞게 사용 - 아이콘 버튼:
aria-label또는sr-only텍스트로 이름 제공 - SVG:
aria-hidden="true"+focusable="false" - 토글 버튼:
aria-pressed="true/false" - 비활성 버튼:
disabled(포커스 불가) 또는aria-disabled(포커스 유지)
확인 문제
문제 1. 좋아요/찜하기 같은 토글 버튼에 적합한 ARIA 속성은?
aria-pressed="true/false"
aria-checked는 체크박스/스위치에, aria-pressed는 버튼 토글에 사용합니다.
문제 2. <a href="#" onclick="openModal()"> 대신 <button>을 써야 하는 이유는?
href="#"은 클릭 시 페이지 최상단으로 이동하거나 URL에 #이 추가됩니다.
모달 열기는 페이지 이동이 아닌 동작이므로 <button>이 적합합니다.
스크린리더도 "버튼"으로 읽어 사용자가 기능을 예측할 수 있습니다.
다음 챕터에서는 카드, 리스트, 내비게이션 컴포넌트의 접근성을 다룹니다. 반복되는 카드 목록에서 접근성 있는 구조를 만드는 방법을 알아봅니다.