시맨틱 HTML과 웹 접근성
웹 접근성이라고 하면 특별한 기술이나 추가 작업을 떠올리기 쉽다. 하지만 실제로 접근성의 상당 부분은 HTML을 원래 의도대로 쓰는 것에서 시작한다. 시맨틱 HTML과 접근성은 별개의 주제가 아니라, 하나의 뿌리에서 나온 두 가지 결과에 가깝다.
시맨틱 HTML이란
HTML 태그에는 두 가지가 담겨 있다. 하나는 기본 동작이고, 다른 하나는 **의미(semantics)**다.
<button>을 예로 들면, 이 태그는 포커스를 받을 수 있고, 키보드의 Enter와 Space로 활성화되며, 폼 안에서 제출 동작을 수행한다. 이건 기본 동작이다. 동시에 브라우저와 보조 기술에게 “이것은 사용자가 누를 수 있는 요소”라는 정보를 전달한다. 이건 의미다.
반면 <div>와 <span>에는 의미가 없다. 의도적으로 그렇게 설계된 순수한 컨테이너다. 그래서 <div>에 클릭 이벤트를 붙여 버튼처럼 만들면, 시각적으로는 동작하지만 키보드 포커스도, 스크린 리더의 역할 안내도, Enter 키 활성화도 없다. tabindex, role, onkeydown 핸들러를 전부 직접 구현해야 비로소 <button> 하나가 기본으로 제공하는 것과 같아진다.
<!-- div로 버튼을 만들면 이만큼 필요하다 -->
<div
role="button"
tabindex="0"
onkeydown="if(event.key==='Enter'||event.key===' ') handleClick()"
onclick="handleClick()"
>
저장
</div>
<!-- 네이티브 버튼은 이게 전부다 -->
<button onclick="handleClick()">저장</button>
시맨틱 태그를 쓴다는 건 코드에 의도를 선언하는 것이다. 브라우저, 스크린 리더, 검색 엔진, 그리고 코드를 읽는 동료가 그 의도를 해석할 수 있게 된다.
문서 구조와 랜드마크
HTML5에서 도입된 구조 태그들은 페이지의 영역에 의미를 부여한다.
<header> <!-- 페이지 또는 섹션의 도입부 -->
<nav> <!-- 네비게이션 링크 모음 -->
<main> <!-- 페이지의 주요 콘텐츠 (문서당 하나) -->
<article> <!-- 독립적으로 배포 가능한 콘텐츠 단위 -->
<section> <!-- 주제별 콘텐츠 그룹 -->
<aside> <!-- 주변 콘텐츠와 간접적으로 관련된 부가 정보 -->
<footer> <!-- 페이지 또는 섹션의 마무리 -->
렌더링 결과만 놓고 보면 <div>와 차이가 없다. 하지만 스크린 리더는 이 태그들을 **랜드마크(landmark)**로 인식한다. 사용자는 “네비게이션으로 이동”, “메인 콘텐츠로 이동” 같은 단축키를 통해 원하는 영역으로 바로 점프할 수 있다.
전부 <div>로 마크업된 페이지에서는 이 기능이 작동하지 않는다. 스크린 리더로 페이지를 탐색하려면 처음부터 끝까지 선형으로 읽어 나가야 한다.
헤딩 계층
헤딩 태그(<h1> ~ <h6>)는 문서의 아웃라인을 구성한다. 스크린 리더 사용자 중 상당수가 페이지를 처음 방문하면 헤딩 목록부터 훑는다. <h1>에서 <h6>까지의 계층이 논리적이면, 문서 전체 구조를 빠르게 파악할 수 있다.
<h1>블로그 제목</h1>
<h2>첫 번째 글</h2>
<h3>소제목</h3>
<h2>두 번째 글</h2>
주의할 점은, 헤딩 레벨을 시각적 크기 때문에 건너뛰지 않는 것이다. <h1> 다음에 <h4>가 오면 아웃라인에 구멍이 생긴다. 글씨 크기는 CSS로 조정하면 되고, 헤딩 레벨은 문서 구조의 깊이를 나타내는 데 써야 한다.
접근성 트리
브라우저는 DOM을 기반으로 두 가지 트리를 구성한다. 하나는 시각적 표현을 위한 렌더 트리이고, 다른 하나는 보조 기술을 위한 **접근성 트리(accessibility tree)**다.
접근성 트리의 각 노드에는 세 가지 핵심 정보가 있다.
- 역할(role) — 이 요소가 무엇인지 (버튼, 링크, 내비게이션 등)
- 이름(name) — 이 요소의 레이블 (텍스트 콘텐츠, aria-label 등)
- 상태(state) — 현재 상태 (비활성, 선택됨, 확장됨 등)
<button disabled>저장</button>은 접근성 트리에서 역할이 button, 이름이 “저장”, 상태가 disabled로 표현된다. 스크린 리더는 이를 “저장, 버튼, 사용 불가”로 읽어준다. 반면 <div class="btn disabled">저장</div>은 역할이 generic이고 상태 정보가 없다. 스크린 리더는 “저장”이라는 텍스트만 읽을 수 있다.
시맨틱 태그를 쓰면 접근성 트리가 자동으로 올바르게 구성된다. 별도의 ARIA 속성 없이도 역할, 이름, 상태가 갖춰지는 것이다.
ARIA
HTML 태그만으로 모든 UI 패턴을 표현할 수는 없다. 탭, 아코디언, 모달, 자동완성 같은 복합 위젯은 HTML 스펙에 대응하는 태그가 없다. 이때 ARIA(Accessible Rich Internet Applications) 속성으로 접근성 트리에 직접 정보를 추가할 수 있다.
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">탭 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">탭 2</button>
</div>
<div role="tabpanel" id="panel-1">탭 1 내용</div>
<div role="tabpanel" id="panel-2" hidden>탭 2 내용</div>
role="tab"은 스크린 리더에게 “이것은 탭이다”라고 알려주고, aria-selected는 선택 상태를, aria-controls는 이 탭이 제어하는 패널을 알려준다.
ARIA에는 첫 번째 규칙이라 불리는 원칙이 있다.
네이티브 HTML 요소로 해결할 수 있으면, ARIA 대신 네이티브를 쓴다.
ARIA는 접근성 트리에 의미만 추가하고, 동작은 추가하지 않기 때문이다. role="button"을 붙여도 포커스가 잡히거나 키보드로 활성화되지는 않는다. 그 동작은 전부 직접 구현해야 한다. 네이티브 태그를 쓰면 의미와 동작이 함께 따라온다.
실무에서 자주 쓰이는 ARIA 속성
aria-label — 시각적 텍스트가 없는 요소에 접근 가능한 이름을 부여한다.
<button aria-label="메뉴 열기">
<svg><!-- 햄버거 아이콘 --></svg>
</button>
aria-hidden="true" — 장식용 요소를 접근성 트리에서 제외한다.
<button>
<svg aria-hidden="true"><!-- 아이콘 --></svg>
저장
</button>
텍스트 “저장”이 이미 이름 역할을 하므로, 아이콘은 스크린 리더에 노출할 필요가 없다.
aria-live — 동적으로 변하는 영역을 스크린 리더에게 알린다.
<div aria-live="polite">
<!-- 이 영역의 콘텐츠가 바뀌면 스크린 리더가 변경 사항을 읽어준다 -->
</div>
polite는 현재 읽고 있는 내용이 끝난 뒤에, assertive는 즉시 끼어들어서 알린다. 토스트 메시지, 검색 결과 갱신, 폼 유효성 피드백 등에 쓰인다.
aria-describedby — 요소에 대한 추가 설명을 연결한다.
<label for="email">이메일</label>
<input type="email" id="email" aria-describedby="email-hint">
<span id="email-hint">예: user@example.com</span>
스크린 리더가 입력 필드에 포커스하면 레이블과 설명을 함께 읽어준다.
폼 접근성
폼은 접근성 문제가 집중되는 영역이다. 가장 기본적이면서 자주 누락되는 것이 <label>이다.
<!-- placeholder는 레이블이 아니다 -->
<input type="email" placeholder="이메일">
<!-- label과 입력 필드를 연결해야 한다 -->
<label for="email">이메일</label>
<input type="email" id="email" placeholder="example@email.com">
placeholder는 입력을 시작하면 사라지고, 스크린 리더에 따라 읽히지 않을 수 있다. <label>은 항상 노출되며, 클릭하면 해당 입력 필드에 포커스가 이동하고, 스크린 리더가 필드와 레이블을 연결하여 읽어준다.
에러 상태도 마찬가지로 프로그래밍적으로 연결해야 한다.
<label for="email">이메일</label>
<input
type="email"
id="email"
aria-describedby="email-error"
aria-invalid="true"
>
<span id="email-error">올바른 이메일 주소를 입력해주세요.</span>
aria-invalid는 이 필드에 오류가 있음을 알리고, aria-describedby는 오류 메시지와 필드를 연결한다. 시각적으로 빨간 테두리를 치는 것만으로는 스크린 리더 사용자에게 정보가 전달되지 않는다.
키보드 접근성
마우스를 사용할 수 없거나 사용하지 않는 상황은 다양하다. 운동 장애, 음성 입력 도구 사용, 또는 단순히 키보드 중심의 워크플로를 선호하는 경우가 있다. 키보드 접근성은 세 가지 원칙으로 요약할 수 있다.
포커스 가능성. 모든 인터랙티브 요소에 키보드 포커스가 도달해야 한다. <button>, <a>, <input> 같은 네이티브 요소는 자동으로 포커스 가능하다. 커스텀 인터랙티브 요소에는 tabindex="0"을 명시해야 한다.
논리적 포커스 순서. Tab 키로 이동하는 순서는 DOM 순서를 따른다. CSS로 시각적 위치를 변경하더라도 포커스 순서는 DOM 순서 그대로이므로, 시각적 배치와 포커스 순서가 어긋나지 않도록 DOM 구조를 설계해야 한다. tabindex에 양수값을 넣어 순서를 강제하는 것은 관리가 어렵고 대부분의 경우 올바른 해결책이 아니다.
포커스 가시성. 현재 포커스된 요소가 시각적으로 구분되어야 한다. 브라우저의 기본 포커스 링을 outline: none으로 제거하는 것은 키보드 사용자의 위치 파악을 불가능하게 만든다.
/* 마우스 클릭에는 나타나지 않고,
키보드 포커스에만 표시된다 */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
:focus-visible은 키보드 탐색 시에만 포커스 스타일을 적용한다. 디자인적 깔끔함과 키보드 접근성을 함께 유지할 수 있는 방법이다.
이미지와 대체 텍스트
alt 속성은 단순하지만 잘못 쓰이는 경우가 많다. 원칙은 명확하다.
<!-- 정보를 전달하는 이미지: 해당 정보를 텍스트로 기술한다 -->
<img src="chart.png" alt="2024년 매출 추이. 1분기 대비 4분기 43% 증가">
<!-- 장식용 이미지: 빈 문자열을 넣는다 -->
<img src="decorative-line.png" alt="">
alt를 아예 생략하면 스크린 리더가 파일명을 읽는다. IMG_4832.jpg라는 파일명에서 의미를 파악할 수 있는 사용자는 없다.
차트나 인포그래픽처럼 복잡한 시각 정보는 alt만으로 부족할 수 있다. 이 경우 본문에서 데이터를 텍스트로 제공하거나, aria-describedby로 상세 설명을 연결하는 방법이 있다.
색상 대비
WCAG 2.1은 텍스트와 배경 사이에 최소 명도 대비를 요구한다. 일반 텍스트는 4.5:1, 큰 텍스트(18pt 이상, 또는 14pt 볼드 이상)는 3:1이다.
/* 대비 비율 2.5:1 — 기준 미달 */
color: #b0b0b0;
background: #ffffff;
/* 대비 비율 4.6:1 — 기준 충족 */
color: #767676;
background: #ffffff;
시각적 차이는 미묘하지만, 저시력 사용자나 밝은 환경에서 화면을 보는 경우에는 가독성에 직접적인 영향을 준다.
색상만으로 정보를 구분하는 것도 피해야 한다. 에러 필드에 빨간 테두리만 표시하는 것은 색각 이상이 있는 사용자에게 정보를 전달하지 못한다. 아이콘, 텍스트, 패턴 등 색상 외의 시각적 단서를 함께 제공해야 한다.
접근성을 고려해야 하는 이유
접근성은 특정 사용자를 위한 별도의 기능이 아니다. WHO 기준으로 전 세계 인구의 약 16%가 어떤 형태든 장애를 가지고 있으며, 시각, 청각, 운동, 인지 등 영역이 다양하다. 일시적인 상황(팔 부상, 강한 직사광선 아래에서의 화면 사용)까지 포함하면, 대부분의 사용자가 어느 시점에는 접근성의 영향을 받는다.
법적 요구사항도 확대되고 있다. 한국의 장애인차별금지법은 웹 접근성 준수를 의무화하고 있고, EU의 European Accessibility Act는 2025년 6월부터 시행된다.
그리고 실용적인 측면에서, 접근성이 좋은 코드는 대체로 구조가 좋은 코드다. 시맨틱 태그를 사용하면 CSS 선택자가 간결해지고, 컴포넌트 경계가 명확해지며, SEO가 개선되고, 자동화 테스트에서 요소를 역할 기반으로 쿼리할 수 있게 된다.
웹 접근성의 핵심은 결국 HTML을 HTML답게 쓰는 것이다. <button>, <label>, <nav>, <h2> — 이미 존재하는 태그를 올바르게 사용하는 것만으로 접근성 문제의 상당 부분이 해결된다. ARIA가 필요한 복합 위젯은 전체 UI의 일부에 불과하다.
접근성은 완성된 제품에 덧붙이는 것이 아니라, 마크업을 작성하는 시점에서 자연스럽게 반영되는 것이 이상적이다. 올바른 태그를 선택하는 습관이 잡히면, 접근성은 추가 비용이 아니라 기본값이 된다.