# 시맨틱 태그 파헤쳐보기

처음 웹 퍼블리싱을 진행할 때 거의 모든 레이아웃을 `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>`로 이동해서 메뉴를 탐색하는 식이다. 시맨틱 태그가 있어야 이 탐색이 가능하다.

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

## 어떤 경우에 어떤 시맨틱 태그를 써야할까?

### `<article>` vs `<section>`, 둘 중 어떤 것으로 래핑을 해야할까?

엘리먼트 구조를 구성할 때, 두 태그 모두 콘텐츠를 묶는 역할로 사용을 하고 있다.
그런데 두 태그를 "언제 어떻게" 사용을 해야하는지에 대해서는 명확하게 사용법을 알고있지 못하는 것 같다.

**핵심 기준은 하나이다, 페이지에서 떼어내도 독립적으로 의미가 있으면 **`**<article>**`**, 아니면 **`**<section>**`**.**

```
<!-- 블로그 포스트 목록 -->
<main>
  <article>                          ← 이 포스트만 따로 읽어도 완결됨
    <h2>시맨틱 HTML이란?</h2>
    <p>...</p>
    <section>                        ← 포스트 안의 특정 섹션 (댓글)
      <h3>댓글</h3>
      <article>                      ← 각 댓글도 독립적 콘텐츠
        <p>좋은 글이에요</p>
      </article>
    </section>
  </article>
</main>
```

`<article>` 안에 `<section>`이 들어갈 수 있고, `<section>` 안에 `<article>`도 들어갈 수 있다. `<article>`은 중첩도 가능하다.

### `<button>` vs `<a>`, 둘 중 어떤 것을 사용해야할까?

```
<!-- 페이지 이동이 목적 → <a> -->
<a href="/posts/1">글 보러가기</a>

<!-- 동작 실행이 목적 → <button> -->
<button type="button" onclick="openModal()">상세보기</button>

<!-- 폼 제출 → <button type="submit"> -->
<button type="submit">저장</button>
```

`<a>`는 링크(URL 이동)용이고, `<button>`은 동작(스크립트, 폼 제출)용이다. `<a href="#">`나 `<a onclick="...">`로 버튼 역할을 구현하는 경우를 종종 봤는데, 스크린리더는 이걸 "링크"로 읽는다. 키보드로 Enter를 눌렀을 때 동작이 예상과 다를 수 있다.

### `<time>` 태그는 어떤 경우에 사용할까?

```
<!-- datetime 속성으로 기계가 읽을 수 있는 형식을 제공 -->
<time datetime="2026-04-27">2026년 4월 27일</time>
<time datetime="2026-04-27T09:00">오전 9시</time>
<time datetime="PT2H30M">2시간 30분</time>
```

화면에 표시되는 텍스트는 "2주 전"처럼 사람이 읽기 편한 형식이어도, `datetime` 속성에 ISO 형식을 넣으면 검색엔진과 보조 기술이 정확한 시각을 파악할 수 있다.

### heading 계층은 순서를 지키는 것이 좋다

```
<!-- 좋은 예 — 계층이 논리적으로 내려간다 -->
<h1>제품 소개</h1>
  <h2>주요 기능</h2>
    <h3>빠른 처리 속도</h3>
    <h3>높은 보안성</h3>
  <h2>가격 안내</h2>

<!-- 나쁜 예 — 시각적 크기 조절 목적으로 계층을 건너뜀 -->
<h1>제품 소개</h1>
<h3>주요 기능</h3>  ← h2를 건너뜀
```

스크린리더 사용자는 heading 목록으로 페이지를 탐색한다. `h1 → h3`처럼 계층이 건너뛰면 탐색 구조가 깨진다. 
만약, 텍스트 크기가 이유라면 CSS로 조정하는 것이 올바르다.

## 시맨틱 태그의 주의사항

### `<section>`은 기본적으로 랜드마크가 아니다

앞서 설명했지만, 접근 가능한 이름(accessible name)이 없는 `<section>`은 스크린리더의 랜드마크 목록에 등록되지 않는다. `<section>`을 쓸 때는 `aria-label`이나 `aria-labelledby`로 이름을 주거나, 제목을 `<h2>~<h6>`로 명확히 제공하는 것이 좋다.

### ARIA role을 중복으로 선언하지 않는 것이 좋다

```
<!-- 불필요한 중복 — <nav>에 role="navigation"은 이미 내장되어 있다 -->
<nav role="navigation">...</nav>

<!-- 그냥 이렇게 쓰면 된다 -->
<nav>...</nav>
```

이미 암묵적 ARIA role이 있는 태그에 동일한 role을 명시적으로 달 필요가 없다. 오히려 코드가 지저분해진다.

### 시맨틱 태그를 쓴다고 기본 스타일이 사라지는 게 아니다

`<h1>`은 기본적으로 `font-size: 2em`, `font-weight: bold`가 들어있다. `<button>`은 브라우저마다 기본 테두리와 배경이 있다. 시맨틱 태그를 쓰면 CSS reset에서 이 기본 스타일을 정리해줘야 하는 경우가 생긴다. 처음 시맨틱 태그로 마크업을 전환했을 때, 의도하지 않은 스타일이 붙어서 당황했던 기억이 있는데, 이 경우에는 CSS reset이나 normalize.css 설정이 되어있는지 먼저 확인하는 것이 좋다.

### `<main>`은 페이지당 하나만 있어야 한다

`<main>` 요소는 문서 내 유일한 주요 콘텐츠 영역이다. 하나의 HTML 문서에 두 개 이상 있으면 안 된다. `hidden` 처리된 `<main>`이 여럿 있는 경우도 W3C 명세상 권장하지 않는다.

### `<article>` 안의 `<header>`, `<footer>`는 banner/contentinfo가 아니다

앞서 설명한 것처럼, `<header>`와 `<footer>`의 ARIA role은 상위 맥락에 따라 달라진다. `<article>` 안에서는 `banner`나 `contentinfo`가 아닌 `generic`이다. 각 `<article>`마다 헤더와 풋터를 자유롭게 쓸 수 있다는 뜻이다.

```
<article>
  <header>                      ← generic role (banner 아님)
    <h2>글 제목</h2>
    <time datetime="2026-04-27">2026년 4월 27일</time>
  </header>
  <p>...</p>
  <footer>                      ← generic role (contentinfo 아님)
    <p>작성자: 김현우</p>
  </footer>
</article>
```

### CSS로 시각적 표현을 바꿔도 의미는 유지된다

시맨틱 태그의 의미는 CSS와 독립적이다. 스타일을 어떻게 바꿔도 태그 자체의 역할은 유지된다. 반대로 시각적으로 비슷하게 만들어도 의미는 달라지지 않는다.

예전에는 레이아웃을 만들기 급급했다면, 현대의 프론트엔드에서는 "접근성" 이라는 키워드가 많이 대두되고 있다. "접근성"을 신경쓰는 것이 아무래도 누구나 이용할 수 있는 좋은 서비스를 만들 수 있는 개발자의 건강한 서비스 마인드가 아닐까 싶다.

이제는 마크업을 짤 때 태그 하나를 고르는 것 자체가 선택이라는 게 보인다. 그냥 `<div>`로 감싸면 편하지만, 어떤 의미를 가진 콘텐츠인지 고민하고 태그를 고르는 습관을 가져야겠다se. 특히 `<section>`에 이름을 붙이지 않으면 랜드마크가 아니라는 부분은 몰랐을 때와 알고 난 뒤가 확실히 다르다. 접근성 도구로 직접 내 페이지를 탐색해보는 것도 꼭 해봐야겠다.

시맨틱 HTML에 대해 더 깊게 파고들고 싶다면 MDN의 [HTML elements reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)와 W3C의 [ARIA in HTML](https://www.w3.org/TR/html-aria/) 명세가 기준이 된다. ARIA 명세 문서는 처음에 읽기 어렵지만, 각 태그의 암묵적 역할을 정확히 확인할 수 있는 가장 정확한 출처다.

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