시맨틱 태그 파헤쳐보기 (1)

현우
  1. 프론트엔드
  2. SEO
처음 웹 퍼블리싱을 진행할 때 거의 모든 레이아웃을 div 태그에 의존하여 구성을 진행했었다. 클래스 이름이나 아이디 값을 붙여 다른 사람들이 잘 이해하면 그렇게 퍼블리싱이 끝난 줄 알았다. 처음 프론트엔드 개발에 본격적으로 진입을 했을 때, HTML이 사실 "의미"를 담는 언어라는 건, 한참 뒤에야 알게 됐다.
시맨틱 태그는 태그 하나하나가 "이 콘텐츠가 어떤 것을 의미하는지" 브라우저와 검색엔진, 스크린리더에게 알려주는 도구이다. <div class="header"><header> 는 눈으로 보기에 같은 의미를 담고 있을 수 있지만, 기계가 읽을 때는 전혀 다르게 수행한다.
당시에는 "잘 동작하니까" 라는 이유로 이 차이점을 체감하지 못했으나, 접근성 이슈에 대해 조금 고찰을 하고나서 의문이 생겼다. 스크린 리더가 내 페이지를 어떻게 읽는지, 그리고 어떤 태그가 실제로 영향을 주는 것인지 다시 한번 시맨틱 태그에 대해 들여다보고자 한다.

div 수프가 표준이던 시절

HTML이 처음 만들어진 건 1991년이다. 팀 버너스리(Tim Berners-Lee)가 문서를 서로 연결하기 위해 설계한 언어였다. 초기에는 구조보다 내용이 중요했다. 폰트 크기를 키우고 싶으면 <font size="5">, 글씨를 굵게 하고 싶으면 <b> 태그를 썼다.
그 시절 웹 개발자들은 테이블로 레이아웃을 만들었다. <table>, <tr>, <td>를 격자처럼 배치해 컬럼 구조를 구현했다. 이후 CSS가 등장하면서 테이블 레이아웃은 줄었지만, 그 자리를 <div>가 채웠다. <div>를 쌓아 레이아웃을 잡는 방식이 표준처럼 자리잡았고, 이를 "div 수프(div soup)"라고 부르게 됐다.
<!-- div 수프 시대의 전형적인 구조 -->
<div class="header">
  <div class="logo">...</div>
  <div class="nav">
    <div class="nav-item">홈</div>
    <div class="nav-item">소개</div>
  </div>
</div>
<div class="content">
  <div class="main">...</div>
  <div class="sidebar">...</div>
</div>
<div class="footer">...</div>
이 구조는 시각적으로는 잘 동작한다. 스타일도 입힐 수 있고, 레이아웃도 자유롭게 잡힌다. 근데 기계 입장에서는 모든 게 그냥 "빈 상자"다. 어디가 헤더인지, 어디가 탐색 영역인지, 어디가 주요 콘텐츠인지 알 수 없다.
2008년 HTML5 초안이 나오면서 상황이 달라졌다. W3C와 WHATWG는 <div> 대신 의미를 가진 구조 태그들을 도입했다. <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>. 이 태그들이 현재 우리가 쓰는 시맨틱 HTML의 기반이다.

시맨틱 태그는 무엇일까?

"시맨틱(semantic)"은 "의미론적인"이라는 뜻이다. 시맨틱 태그는 콘텐츠의 역할과 의미를 태그 이름 자체로 나타내는 HTML 요소다. <div>는 아무 의미가 없는 컨테이너다. <header>는 "이 안의 내용이 헤더다"라고 선언한다. 코드를 보는 사람도, 브라우저도, 검색엔진도, 스크린리더도 그 의미를 이해할 수 있다.

자주 쓰는 시맨틱 태그들

페이지 구조를 잡는 태그들
태그
역할
<header>
소개 콘텐츠나 탐색 링크의 컨테이너. 페이지 전체 헤더나 섹션 헤더로 쓰인다
<nav>
탐색 링크 묶음. 주요 메뉴, 사이드 링크 목록 등
<main>
페이지의 주요 콘텐츠 영역. 페이지당 하나만 존재해야 한다
<article>
독립적으로 배포하거나 재사용할 수 있는 콘텐츠 단위. 블로그 포스트, 뉴스 기사, 댓글 등
<section>
주제별로 묶인 콘텐츠 구역. 제목(<h2>~<h6>)이 있어야 의미가 명확해진다
<aside>
주요 콘텐츠와 간접적으로 연관된 부수적인 콘텐츠. 사이드바, 광고, 관련 링크 등
<footer>
저작권, 연락처, 관련 링크 등 마무리 정보
콘텐츠를 표현하는 태그들
태그
역할
<h1>~<h6>
제목 계층. 문서 구조의 뼈대 역할
<p>
문단
<figure>
이미지, 다이어그램, 코드 등 독립적인 콘텐츠 묶음
<figcaption>
<figure> 내 캡션
<time>
날짜나 시간. datetime 속성으로 기계가 읽을 수 있는 형식 지정
<address>
가장 가까운 <article> 또는 <body>의 연락처 정보
<details>
열고 닫을 수 있는 콘텐츠 컨테이너
<summary>
<details>의 요약 제목
<mark>
현재 문맥에서 관련성이 있어 강조된 텍스트
<abbr>
약어나 두문자어. title 속성으로 전체 단어 설명
인터랙션과 폼
태그
역할
<button>
클릭 가능한 버튼
<a>
하이퍼링크. href가 있으면 링크, 없으면 플레이스홀더
<form>
사용자 입력 폼
<input>
입력 필드. type 속성으로 역할이 결정된다
<label>
폼 요소의 레이블
<fieldset>
관련 폼 컨트롤 묶음
<legend>
<fieldset>의 제목

시맨틱 태그를 쓰면 무엇이 달라질까?

SEO — 검색엔진이 구조를 이해한다

검색엔진 크롤러는 HTML을 파싱해서 콘텐츠를 인덱싱한다. 이때 <h1> 안의 텍스트와 <p> 안의 텍스트, <nav> 안의 링크를 각각 다르게 처리한다. <div class="title"> 안에 제목을 넣으면 검색엔진은 그게 제목인지 본문인지 알 수 없다. <h1> 안에 넣으면 "이 페이지의 핵심 주제"로 해석한다. 시맨틱 태그는 크롤러에게 페이지 구조를 전달하는 신호다.

접근성 — 스크린리더가 페이지를 탐색한다

스크린리더는 HTML 구조를 읽어서 시각 장애인 사용자에게 전달한다. 이때 시맨틱 태그는 두 가지 역할을 한다.
1.
랜드마크(landmark) 탐색이다.
스크린리더 사용자는 페이지 전체를 순서대로 읽는 대신, 랜드마크를 건너뛰며 탐색할 수 있다. <main>으로 바로 이동하거나, <nav>로 이동해서 메뉴를 탐색하는 식이다. 시맨틱 태그가 있어야 이 탐색이 가능하다.
2.
역할(role) 전달
<button>은 스크린리더가 "버튼입니다"라고 읽는다. <div onClick="...">은 "그룹"이나 아무 안내 없이 지나칠 수 있다. 사용자가 클릭 가능한지조차 모를 수 있다.

유지보수 — 코드 가독성이 높아진다

<!-- before: 클래스 이름을 모르면 구조를 파악하기 어렵다 -->
<div class="wrap">
  <div class="hd">
    <div class="gnb">...</div>
  </div>
  <div class="ct">
    <div class="lnb">...</div>
    <div class="main-ct">...</div>
  </div>
</div>

<!-- after: 태그만 봐도 구조가 보인다 -->
<body>
  <header>
    <nav>...</nav>
  </header>
  <main>
    <aside>...</aside>
    <article>...</article>
  </main>
</body>
처음 합류한 프로젝트에서 클래스 이름이 ct, hd, lnb로 약어화된 코드를 본 적이 있다. 시맨틱 태그는 그런 내부 규약을 몰라도 구조를 파악할 수 있게 해준다.

시맨틱 태그의 암묵적 ARIA 역할

ARIA(Accessible Rich Internet Applications)는 HTML 요소에 접근성 정보를 추가하는 명세다. role, aria-label, aria-hidden 같은 속성으로 스크린리더에게 추가 정보를 전달한다.
💬
ARIA (Accessible Rich Internet Appliations)
웹 콘텐츠와 웹 앱의 접근성을 높이기 위해 역할(role), 상태(state), 속성(property)을 HTML에 추가하는 W3C 표준. 시각적으로는 보이지 않지만 보조 기술이 읽는 메타데이터를 제공한다.
근데 시맨틱 태그를 쓰면 명시적으로 role을 선언하지 않아도, 브라우저가 자동으로 역할을 부여한다. 이걸 암묵적 ARIA 역할(implicit ARIA role)이라고 한다.
💬
implicit ARIA role
HTML 요소가 기본으로 갖고 있는 ARIA 역할을 의미한다. role 속성을 직접 쓰지 않아도 브라우저가 접근성 트리(accessibility tree)에 자동으로 해당 역할을 등록한다.
<!-- 이 두 코드는 스크린리더 입장에서 같다 -->
<nav>...</nav>
<div role="navigation">...</div>

<!-- 이 두 코드도 같다 -->
<button>클릭</button>
<div role="button" tabindex="0">클릭</div>

주요 시맨틱 태그와 암묵적 ARIA 역할

태그
암묵적 ARIA role
조건
<header>
banner
<article>, <aside>, <main>, <nav>, <section> 외부에 있을 때
<header>
generic
<article>, <section> 등 sectioning 요소 안에 있을 때
<footer>
contentinfo
sectioning 요소 외부에 있을 때
<footer>
generic
sectioning 요소 안에 있을 때
<nav>
navigation
항상
<main>
main
항상
<article>
article
항상
<section>
region
접근 가능한 이름(accessible name)이 있을 때
<section>
generic
접근 가능한 이름이 없을 때
<aside>
complementary
항상
<h1>~<h6>
heading
항상
<ul>, <ol>
list
항상
<li>
listitem
항상
<button>
button
항상
<a href="...">
link
href 속성이 있을 때
<a>
generic
href 속성이 없을 때
<form>
form
접근 가능한 이름이 있을 때
<form>
generic
접근 가능한 이름이 없을 때
<input type="text">
textbox
항상
<input type="checkbox">
checkbox
항상
<input type="radio">
radio
항상
<select>
listbox
항상
<table>
table
항상
<figure>
figure
항상
<details>
group
항상
<dialog>
dialog
항상
<img alt="...">
img
alt가 빈 문자열이 아닐 때
<img alt="">
presentation
alt가 빈 문자열일 때
처음에 이 표를 봤을 때, <section>의 동작이 제일 헷갈렸다. 같은 <section> 태그인데 이름이 있으면 region, 없으면 generic이라고 할 때 실제로 아무 이름 없는 <section>은 스크린리더 랜드마크 목록에 아예 뜨지 않는다.
<!-- region이 되는 section — 스크린리더 랜드마크에 등록됨 -->
<section aria-label="최근 공지사항">
  <h2>최근 공지사항</h2>
  ...
</section>

<!-- 또는 -->
<section aria-labelledby="notice-heading">
  <h2 id="notice-heading">최근 공지사항</h2>
  ...
</section>

<!-- generic이 되는 section — 랜드마크에 등록되지 않음 -->
<section>
  <h2>최근 공지사항</h2>
  ...
</section>
<header><footer>도 마찬가지다. 페이지 최상위에 있을 때는 banner / contentinfo가 되지만, <article> 안에 들어가면 일반 컨테이너(generic)가 된다. 상위 맥락에 따라 역할이 바뀐다.
<!-- banner role -->
<body>
  <header>...</header>

<!-- generic role (banner가 아님) -->
<body>
  <article>
    <header>...</header>  ← 여기서는 그냥 generic
  </article>

<div>와 시맨틱 태그는 어떻게 다를까?

구분
<div> · <span>
시맨틱 태그
의미
없음 (순수 컨테이너)
콘텐츠 역할을 명시
기본 스타일
없음
브라우저마다 기본 스타일 존재 (<h1>의 font-size 등)
ARIA role
없음 (generic)
암묵적 role 자동 부여
키보드 접근성
없음
<button>, <a> 등은 기본으로 포커스 가능
SEO 영향
낮음
<h1>, <article>, <main> 등은 크롤러가 특별히 처리
<div><span>을 완전히 배제할 수는 없다. 순수하게 스타일이나 JavaScript 조작을 위한 래퍼가 필요할 때는 여전히 <div>가 맞다. 하지만 구조적 의미가 있는 영역에 <div>를 쓰는 건 태그의 의미있는 정보를 지워버리는 것과 같다.
悠悠自適
유유자적한 블로그 주인장 김현우입니다.
오늘도 안녕하신가요?
Subscribe
1
👍
현우
See latest comments