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

처음 웹 퍼블리싱을 진행할 때 거의 모든 레이아웃을 `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)"라고 부르게 됐다.

```javascript
<!-- 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>`로 이동해서 메뉴를 탐색하는 식이다. 시맨틱 태그가 있어야 이 탐색이 가능하다.

1. **역할(role) 전달** 

`<button>`은 스크린리더가 "버튼입니다"라고 읽는다. `<div onClick="...">`은 "그룹"이나 아무 안내 없이 지나칠 수 있다. 사용자가 클릭 가능한지조차 모를 수 있다.

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

```javascript
<!-- 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)에 자동으로 해당 역할을 등록한다.

```javascript
<!-- 이 두 코드는 스크린리더 입장에서 같다 -->
<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>`은 스크린리더 랜드마크 목록에 아예 뜨지 않는다.

```javascript
<!-- 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)가 된다. 상위 맥락에 따라 역할이 바뀐다.

```javascript
<!-- 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>`를 쓰는 건 태그의 의미있는 정보를 지워버리는 것과 같다.

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
