의미 있는 HTML 쓰기: 대부분이 하는 시맨틱 HTML 실수 7가지

화면에서는 똑같아 보여도 div로 만든 버튼, label 없는 입력칸, 크기로 고른 제목 태그는 키보드 사용자와 스크린 리더, 검색 엔진에게 전혀 다르게 보인다. 직접 눌러볼 수 있는 예제로 흔한 시맨틱 HTML 실수와 고치는 법을 정리했다.

보기엔 똑같은데, 뭐가 다를까?

아래 미리보기에는 카드 두 개가 있다. 하나는 전부 div로, 하나는 의미에 맞는 태그로 만들었다. 겉모습은 완전히 같다. 아래 html 탭에서 코드를 비교해보자.

마우스로 클릭하면 둘 다 잘 동작한다. 그런데 미리보기 안을 한 번 클릭하고 Tab 키를 눌러보자. 포커스는 두 번째 카드의 버튼으로만 간다. div로 만든 버튼은 키보드로는 아예 닿을 수 없다.

시맨틱 HTML(Semantic HTML)은 "어떻게 보이는지"가 아니라 "무엇인지"를 기준으로 태그를 고르는 것이다. 모양은 CSS가 담당하고, 태그는 의미를 담당한다.

화면을 눈으로 보는 사람에게는 차이가 없지만, HTML을 읽는 쪽에게는 차이가 크다.

누가 HTML을 읽나의미 있는 태그를 쓰면
키보드 사용자Tab으로 버튼, 링크, 입력칸을 오갈 수 있다
스크린 리더(Screen Reader)"버튼", "제목 2단계", "탐색 영역"처럼 역할을 읽어준다
검색 엔진본문, 제목, 내비게이션을 구분해서 핵심 내용을 파악한다
브라우저읽기 모드, 자동 번역, 자동완성이 더 정확해진다
다음에 코드를 볼 개발자div class="header"보다 header가 한눈에 읽힌다

이제 실무에서 정말 자주 보이는 실수들을 하나씩 보자.


실수 1. 클릭되는 div

가장 흔하고, 가장 영향이 큰 실수다.

<!-- ❌ -->
<div class="btn" onclick="save()">저장</div>

<!-- ✅ -->
<button type="button" onclick="save()">저장</button>

div로 버튼을 제대로 흉내 내려면 이만큼이 필요하다.

  • tabindex="0"을 붙여 키보드 포커스를 받게 하고
  • role="button"으로 스크린 리더에 버튼이라고 알리고
  • Enter와 Space 키 이벤트를 직접 처리하고
  • 비활성화 상태(disabled)도 직접 구현한다

button 태그 하나면 이 모든 게 기본으로 따라온다. 아래 미리보기 안을 클릭한 다음 Tab으로 포커스를 옮기고 Enter를 눌러보자.

form 안의 button은 기본 type이 submit이다. 폼을 제출하지 않는 버튼이라면 type="button"을 꼭 적어주자. 그렇지 않으면 누를 때마다 폼이 제출된다.


실수 2. 링크와 버튼을 섞어 쓰기

a와 button은 둘 다 "누르는 것"이라 헷갈리기 쉽지만, 기준은 간단하다.

다른 곳으로 이동하면 a, 현재 화면에서 무언가를 실행하면 button.

<!-- ❌ 이동하지 않는데 링크를 씀 -->
<a href="#" onclick="openModal()">로그인</a>

<!-- ✅ 화면 안에서 동작하니 버튼 -->
<button type="button" onclick="openModal()">로그인</button>

<!-- ❌ 페이지 이동인데 버튼을 씀 -->
<button onclick="location.href='/pricing'">요금제 보기</button>

<!-- ✅ 이동이니 링크 -->
<a href="/pricing">요금제 보기</a>

링크를 버튼으로 만들면 새 탭에서 열기, 주소 복사, 검색 엔진의 링크 수집이 모두 안 된다. 반대로 href="#" 링크를 버튼처럼 쓰면 누를 때 페이지가 맨 위로 튀거나 주소에 #이 붙는다.


실수 3. 글자 크기로 제목 태그 고르기

"여기는 글씨가 작아야 하니까 h4"처럼 크기로 고르는 경우가 많다. 하지만 h1~h6은 문서의 목차(Outline) 를 만드는 태그다. 스크린 리더 사용자는 제목만 골라 듣는 기능으로 페이지를 훑어본다.

<!-- ❌ 크기에 맞춰 고르다 보니 단계가 뒤죽박죽 -->
<h1>쇼핑몰</h1>
<h4>오늘의 추천</h4>
<h2>상품 이름</h2>

<!-- ✅ 구조대로 쓰고, 크기는 CSS로 -->
<h1>쇼핑몰</h1>
<h2 class="section-title">오늘의 추천</h2>
<h3>상품 이름</h3>
  • 페이지의 주제를 나타내는 h1은 하나만 두는 것이 무난하다.
  • 단계는 건너뛰지 않는다 (h2 다음에 바로 h4 ❌).
  • 크기를 바꾸고 싶으면 태그가 아니라 CSS를 바꾼다.

실수 4. label 없는 입력칸

디자인이 깔끔해 보인다는 이유로 placeholder만 쓰고 label을 빼는 경우가 많다. 미리보기에서 위아래 체크박스의 글자 부분을 클릭해보자.

label이 없는 첫 번째 체크박스는 작은 네모를 정확히 눌러야만 체크된다. label이 있으면 글자를 눌러도 체크되고, 이메일이라는 글자를 누르면 입력칸에 바로 포커스가 간다. 모바일에서는 이 차이가 특히 크다.

placeholder만으로 안 되는 이유는 이렇다.

  • 입력을 시작하면 사라져서, 무엇을 입력하던 칸인지 잊어버린다.
  • 글자색이 흐려서 잘 안 읽힌다.
  • 스크린 리더가 칸의 이름으로 제대로 읽지 못하는 경우가 있다.

디자인상 label을 보이고 싶지 않다면 지우지 말고 화면에서만 숨기는 CSS(흔히 sr-only라는 클래스 이름을 쓴다)를 적용하자. 눈에는 안 보여도 스크린 리더는 읽을 수 있다.


실수 5. 이미지 alt를 빼먹거나 의미 없이 채우기

alt는 이미지가 보이지 않을 때(스크린 리더, 이미지 로딩 실패, 검색 엔진) 그 자리를 대신하는 텍스트다.

상황이렇게 쓴다
내용을 전달하는 이미지이미지가 전하는 내용을 문장으로: alt="3분기 매출이 20% 늘어난 막대그래프"
장식용 이미지 (배경 무늬, 아이콘 옆 장식)빈 값: alt="" (스크린 리더가 건너뜀)
링크나 버튼 안의 아이콘동작을 설명: alt="장바구니로 이동"
<!-- ❌ alt가 없으면 스크린 리더가 파일 이름을 읽기도 한다 ("아이엠지 공일이삼 점 피엔지") -->
<img src="img_0123.png" />

<!-- ❌ 의미 없는 alt -->
<img src="chart.png" alt="이미지" />

<!-- ✅ -->
<img src="chart.png" alt="3분기 매출이 20% 늘어난 막대그래프" />
<img src="divider.svg" alt="" />

alt 속성을 아예 빼는 것과 빈 값(alt="")으로 두는 것은 다르다. 빈 값은 "장식이니 건너뛰어도 된다"는 뜻이고, 빠진 건 "깜빡했다"는 뜻이다.


실수 6. 목록을 div로 나열하기

메뉴, 태그 목록, 검색 결과처럼 같은 종류가 반복되면 목록이다.

<!-- ❌ -->
<div class="menu">
  <div><a href="/">홈</a></div>
  <div><a href="/blog">블로그</a></div>
  <div><a href="/about">소개</a></div>
</div>

<!-- ✅ -->
<nav aria-label="주요 메뉴">
  <ul>
    <li><a href="/">홈</a></li>
    <li><a href="/blog">블로그</a></li>
    <li><a href="/about">소개</a></li>
  </ul>
</nav>

목록 태그를 쓰면 스크린 리더가 "목록, 항목 3개"라고 먼저 알려줘서, 사용자가 얼마나 긴 목록인지 미리 알 수 있다. 순서가 중요하면(설치 단계, 순위) ol을 쓴다.


실수 7. 페이지 뼈대를 전부 div로 만들기

header, nav, main, footer 같은 태그는 랜드마크(Landmark)라고 불린다. 스크린 리더 사용자는 이걸로 "본문으로 바로 이동", "메뉴로 이동" 같은 점프를 한다. 지하철역 출구 번호 같은 역할이다.

이렇게 쓰고 있다면이렇게 바꾸자의미
<div class="header"><header>사이트 로고, 상단 영역
<div class="nav"><nav>주요 메뉴, 이동 링크 묶음
<div class="main"><main>페이지의 핵심 내용 (페이지당 하나)
<div class="post"><article>그 자체로 완결된 콘텐츠 (글, 댓글, 상품 카드)
<div class="section"><section>제목이 있는 주제별 묶음
<div class="sidebar"><aside>본문과 관련은 있지만 부가적인 내용
<div class="footer"><footer>저작권, 연락처 등 하단 정보
<body>
  <header>
    <a href="/">로고</a>
    <nav aria-label="주요 메뉴">...</nav>
  </header>

  <main>
    <article>
      <h1>글 제목</h1>
      <section>
        <h2>첫 번째 소주제</h2>
        <p>...</p>
      </section>
    </article>
    <aside>관련 글</aside>
  </main>

  <footer>© 2026</footer>
</body>

반대로 모든 div를 section으로 바꾸는 것도 실수다. section은 제목이 있는 의미 단위일 때만 쓴다. 그냥 스타일을 위해 감싸는 상자라면 div가 맞다. div는 나쁜 태그가 아니라 "특별한 의미가 없는 상자"라는 뜻이다.


ARIA는 마지막 수단이다

접근성을 챙기다 보면 role, aria-label 같은 ARIA 속성을 만나게 된다. 강력하지만 첫 번째 규칙이 있다.

네이티브 HTML 요소로 해결된다면, ARIA를 쓰지 말고 그 요소를 써라.

<div role="button" tabindex="0">보다 <button>이, <div role="navigation">보다 <nav>가 낫다. ARIA는 "이건 버튼이에요"라고 말만 해줄 뿐, 키보드 동작은 하나도 만들어주지 않는다. 잘못 쓴 ARIA는 안 쓴 것보다 더 혼란스럽다.


내 페이지는 괜찮을까? 3분 점검법

방법확인할 것
마우스를 내려놓고 Tab 키로만 사용해보기모든 버튼과 링크에 닿는지, 포커스 테두리가 보이는지
개발자 도구 → Lighthouse → 접근성(Accessibility) 검사alt 누락, label 누락, 색 대비 문제
개발자 도구 → Elements → Accessibility 탭각 요소가 어떤 역할(role)과 이름으로 읽히는지
제목만 뽑아보기 (콘솔에서 아래 코드 실행)제목 단계가 건너뛰지 않는지
// 콘솔에 붙여넣으면 페이지의 제목 구조가 들여쓰기로 출력된다
document.querySelectorAll("h1,h2,h3,h4,h5,h6").forEach((h) =>
  console.log(" ".repeat((h.tagName[1] - 1) * 2) + h.tagName + " " + h.textContent.trim())
);

정리: 왜 알아두면 좋은가

실수고치는 법
클릭되는 divbutton
href="#" 링크로 동작 실행이동은 a, 동작은 button
크기로 고른 제목 태그구조대로 h1→h2→h3, 크기는 CSS
placeholder만 있는 입력칸label 연결
없거나 의미 없는 alt내용 설명, 장식이면 alt=""
div로 나열한 목록ul/ol + li
div로만 만든 페이지 뼈대header, nav, main, article, footer

시맨틱 HTML은 따로 시간을 내서 하는 "접근성 작업"이 아니다. 처음 태그를 고를 때 한 번 더 생각하는 습관이다. 그 습관 하나로 키보드 사용자와 스크린 리더 사용자는 사이트를 쓸 수 있게 되고, 검색 엔진은 내용을 더 잘 이해하고, 나중에 코드를 읽는 동료는 구조를 한눈에 파악한다. 다음에 div를 쓰려는 순간, "이게 정말 의미 없는 상자인가?"부터 물어보자.