한 줄 요약 —
<table>은 자료를 행과 열로 보여 주는 표일 때만 씁니다. 그리고 모든 태그가 공통으로 가지는 네 속성 —id(이름표),class(같은 부류 표시),data-*(부가 정보),aria-*(보조기기 안내) — 가 각각 무엇을 하는지를 영양성분표 한 장을 만들며 정리합니다.
학습 목표
<table>을 표가 정말 표일 때만 써야 하는 이유를 설명할 수 있다.<thead>·<tbody>·<th>·<td>의 역할을 구분해서 쓸 수 있다.<th>에scope속성을 붙여 보조기기가 머리글의 방향을 알도록 만든다.- 모든 태그가 공통으로 가지는
id·class·data-*·aria-*속성의 역할을 안다. - 한 페이지 안에서
id는 단 하나만 쓰는 규칙을 지킨다.
오늘의 비유 — 엑셀 표는 표일 때만 표다
엑셀 시트를 떠올려 봅니다. 우리는 가계부, 사원 명단, 시간표처럼 칸칸이 줄을 맞춘 자료를 정리할 때 엑셀을 켭니다. 행과 열이 격자처럼 만나 하나의 셀이 되고, 맨 윗줄에는 "이름", "월급", "부서" 같은 머리글이 적혀 있죠. 이 머리글이 있어야 셀에 적힌 숫자나 글자가 무엇을 뜻하는지 분명해집니다.
반대로 엑셀로 결혼식 청첩장이나 회사 로고를 만드는 일은 어색합니다. 칸이 격자라서 보기엔 가지런하지만, 그건 표가 아니라 모양이 격자처럼 생긴 그림일 뿐입니다. HTML의 <table>도 똑같습니다. 자료를 행과 열로 보여 줄 때만 <table>을 쓰고, 페이지의 칸을 나누어 디자인하는 데는 다른 도구를 씁니다.
오늘 후반부에 보는 글로벌 속성도 같은 세계관입니다. 엑셀의 셀에 이름을 붙이고, 같은 부류끼리 색을 통일하고, 셀에 작은 메모를 달고, 머리글로 셀의 뜻을 안내하는 일 — 이 네 가지가 HTML에서는 각각 id·class·data-*·aria-*로 나타납니다.
핵심 개념
table — 행과 열이 모두 의미를 가질 때만
<table>은 행과 열이 만나는 자리에 의미가 있는 자료에 씁니다. 영양성분표가 좋은 예입니다. 각 행이 "탄수화물", "단백질", "지방" 같은 영양소를 나타내고, 각 열이 "1회 제공량", "하루 권장량 대비" 같은 기준을 나타냅니다. 둘 다 머리글이 있어야 셀의 숫자가 무엇을 뜻하는지 알 수 있습니다.
<table>
<thead>
<tr>
<th scope="col">영양소</th>
<th scope="col">1회 제공량</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">탄수화물</th>
<td>25 g</td>
</tr>
<tr>
<th scope="row">단백질</th>
<td>3 g</td>
</tr>
</tbody>
</table>
표를 만드는 태그 네 개의 역할을 한 번에 봅니다.
<tr>은 한 줄, 즉 한 행(table row)입니다.<th>는 머리글 칸(table header)입니다. 진하게·가운데 정렬로 자동 표시됩니다.<td>는 일반 자료 칸(table data)입니다.<thead>와<tbody>는 머리글 줄과 본문 줄을 묶어 줍니다. 표가 길어져 인쇄하거나 스크롤할 때 머리글이 따로 동작해야 할 자리에서 특히 유용합니다.
scope — 머리글이 어느 방향을 가리키는가
<th>에 붙인 scope는 그 머리글이 열의 머리인지, 행의 머리인지를 표시합니다. 위 코드에서 "영양소"·"1회 제공량"은 그 아래 칸들의 머리이므로 scope="col"(열), "탄수화물"·"단백질"은 그 오른쪽 칸들의 머리이므로 scope="row"(행)입니다.
화면으로 보는 사람은 자리만 봐도 "이건 위쪽 머리고 저건 왼쪽 머리"라고 알 수 있지만, 화면을 읽어 주는 보조기기는 그 자리를 직접 보지 못합니다. scope가 있어야 "탄수화물"이라는 행 머리와 "25g"이라는 셀을 묶어 "탄수화물, 1회 제공량, 25g"처럼 의미가 통하게 읽어 줍니다.
글로벌 속성 — 모든 태그가 공통으로 가지는 네 가지
지금까지 본 <h1>, <p>, <img>, <input>, <table> 같은 태그들은 모두 같은 네 가지 속성을 가질 수 있습니다. 한 태그에만 붙이는 특별 속성이 아니라 거의 모든 태그에 두루 쓰는 속성이라는 뜻에서 글로벌(global) 속성이라고 부릅니다. 엑셀 셀에 빗대 보면 다음과 같습니다.
id— 셀 이름. 한 시트 안에서 단 하나만 가질 수 있는 고유 이름.class— 같은 부류 표시. "급여 셀", "할인가 셀"처럼 여러 셀에 같은 표시를 붙임.data-*— 셀 메모. 셀에 살짝 적어 두는 부가 정보.aria-*— 보조기기 안내. 화면을 읽어 주는 사람에게 셀의 뜻을 알려 주는 라벨.
id — 한 페이지에 단 하나뿐인 이름표
<h2 id="ingredients">원재료</h2>
id는 그 페이지에서 유일해야 합니다. 같은 id가 두 곳에 있으면 "어느 칸을 가리키는지"가 모호해집니다. id는 페이지 안의 특정 자리로 바로 이동할 때 자주 쓰입니다. 위의 id="ingredients"가 있으면, 주소창의 URL 끝에 #ingredients를 붙여 그 자리로 한 번에 스크롤할 수 있습니다.
class — 같은 부류임을 표시
<td class="warning">3 g</td>
<td class="warning">5 g</td>
class는 여러 칸에 똑같은 표시를 붙입니다. 위처럼 "주의가 필요한 셀"이라는 부류를 만들어 두면, CSS에서 .warning이라는 한 줄로 모든 주의 셀의 색을 한꺼번에 빨갛게 만들 수 있습니다. 한 태그에 여러 부류를 동시에 붙일 수도 있습니다. class="warning highlighted"처럼 빈칸으로 나란히 적습니다.
data-* — 내가 만들어 붙이는 부가 정보
<tr data-row-id="42" data-category="protein">
<th scope="row">단백질</th>
<td>3 g</td>
</tr>
data-로 시작하는 속성은 우리가 직접 이름을 붙여 만드는 부가 정보 칸입니다. 화면에는 보이지 않지만, 이 자리에 적어 둔 값을 나중에 CSS나 JavaScript가 꺼내 쓸 수 있습니다. 예시의 data-row-id와 data-category는 우리가 그 자리에서 의미를 정한 이름이고, HTML이 미리 정해 둔 단어가 아닙니다.
aria-* — 보조기기에게 보내는 안내문
<button aria-label="영양성분 표 펼치기">▼</button>
aria-로 시작하는 속성은 화면을 읽어 주는 보조기기를 위한 안내입니다. 위의 버튼은 화면에 "▼" 한 글자만 보이지만, 보조기기는 글자 그대로 "아래쪽 화살표"라고 읽거나 아예 못 읽을 수 있습니다. aria-label="영양성분 표 펼치기"가 있으면 보조기기는 "영양성분 표 펼치기, 버튼"이라고 말 그대로 읽어 줍니다.
다만 aria-*는 시맨틱 태그가 일을 못 할 때만 보강용으로 씁니다. 예를 들어 <button>닫기</button>처럼 글자가 이미 있다면 aria-label을 또 붙일 필요가 없습니다. 이중으로 표시하면 오히려 두 번 읽혀 어색해집니다.
함께 따라하기 — 영양성분표 한 장 만들기
day-11 폴더에 index.html을 두고, <body> 안에 <h1>오늘의 도시락 영양성분표</h1>을 둔 다음 그 아래를 채웁니다.
<table>
<thead>
<tr>
<th scope="col">영양소</th>
<th scope="col">1회 제공량</th>
<th scope="col">하루 권장량 대비</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">탄수화물</th>
<td>52 g</td>
<td class="highlight" data-percent="18">18%</td>
</tr>
<tr>
<th scope="row">단백질</th>
<td>23 g</td>
<td class="highlight" data-percent="46">46%</td>
</tr>
<tr>
<th scope="row">지방</th>
<td>14 g</td>
<td class="highlight" data-percent="22">22%</td>
</tr>
</tbody>
</table>
저장하고 브라우저로 열어보면 머리글 줄이 진하게 표시된 표 하나가 보입니다. 첫 줄은 "영양소 / 1회 제공량 / 하루 권장량 대비"이고, 그 아래로 탄수화물·단백질·지방의 세 줄이 이어집니다.
이제 두 가지를 직접 확인해 봅니다. 첫째, 개발자 도구의 Elements 탭에서 "하루 권장량 대비"의 셀 하나를 클릭하면, 오른쪽에 data-percent="46" 같은 우리가 적어 둔 값이 그대로 남아 있는 것을 볼 수 있습니다. 화면에는 보이지 않지만 자료는 거기에 붙어 있다는 뜻입니다. 둘째, 운영체제의 화면 낭독기(맥의 VoiceOver, 윈도의 내레이터)를 켜고 표 위를 한 칸씩 옮겨 보면, "단백질, 1회 제공량, 23 그램"처럼 머리글과 칸의 값을 묶어서 읽어 줍니다 — scope가 한 일입니다.
흔한 실수 3가지
1. 레이아웃 용도로 table을 쓴다
페이지의 칸을 나누고 싶을 때 <table>로 격자를 만드는 경우입니다. 옛날 자료가 많이 이렇게 가르쳤기 때문에 지금도 종종 보입니다.
<table>
<tr>
<td><img src="/logo.png" alt="회사 로고"></td>
<td><nav>메뉴</nav></td>
</tr>
<tr>
<td colspan="2"><p>본문…</p></td>
</tr>
</table>
(이 코드는 화면으로 보면 멀쩡하지만 의미가 어긋나 있습니다.) 표는 행과 열이 자료의 가로축·세로축을 뜻할 때 씁니다. 로고와 메뉴, 본문은 행과 열의 관계가 아니라 그냥 "위에 머리, 아래에 본문"입니다. 보조기기는 이 코드를 진짜 표로 받아들여 "1행 1열, 회사 로고. 1행 2열, 메뉴…"처럼 읽어 사용자가 페이지를 헷갈리게 됩니다. 모바일 화면에서 셀이 너무 좁아져 가독성이 무너지는 문제도 같이 옵니다.
<header>
<img src="/logo.png" alt="회사 로고">
<nav>메뉴</nav>
</header>
<main>
<p>본문…</p>
</main>
행과 열의 관계가 아니라 "머리·본문" 같은 의미 구조라면, 8회의 시맨틱 태그(<header>, <main>)로 묶고 가로 배치는 다음 회차의 CSS로 만듭니다. <table>은 영양성분표·시간표·가격표처럼 행과 열이 모두 의미를 가질 때만 씁니다.
2. th에 scope를 안 적어 스크린리더가 헷갈린다
표는 정성껏 만들었는데 <th>에 scope를 안 붙인 경우입니다.
<table>
<tr>
<th>영양소</th>
<th>1회 제공량</th>
</tr>
<tr>
<th>탄수화물</th>
<td>25 g</td>
</tr>
</table>
(이 코드는 화면으로는 멀쩡합니다만 보조기기가 머리글의 방향을 알 수 없습니다.) 화면으로 보는 사람은 "영양소"가 위에 있으니 열 머리고, "탄수화물"이 왼쪽에 있으니 행 머리라고 짐작합니다. 하지만 보조기기는 자리를 직접 보지 못해 두 <th> 중 어느 쪽이 어느 방향의 머리인지 모릅니다. "25g"이라는 값을 읽을 때 어느 머리글을 함께 들려줄지 정할 수 없게 됩니다.
<table>
<tr>
<th scope="col">영양소</th>
<th scope="col">1회 제공량</th>
</tr>
<tr>
<th scope="row">탄수화물</th>
<td>25 g</td>
</tr>
</table>
scope="col"은 그 머리글이 자기 아래의 셀들을 가리킨다는 뜻이고, scope="row"는 자기 오른쪽의 셀들을 가리킨다는 뜻입니다. 두 줄짜리 표라도 scope를 빠뜨리지 않고 붙이는 습관을 들이면, 표가 커져도 그대로 일관되게 갈 수 있습니다.
3. id를 한 페이지에 두 번 이상 쓴다
id를 마치 class처럼, 비슷한 칸 여러 곳에 똑같이 붙이는 경우입니다.
<h2 id="section">탄수화물</h2>
<!-- 한참 아래 -->
<h2 id="section">단백질</h2>
<h2 id="section">지방</h2>
(이 코드는 한 페이지 안에 같은 이름표를 세 군데 붙인 셈이라 동작이 어그러집니다.) 같은 id가 여럿이면 주소창의 #section이 어느 자리로 갈지 정해져 있지 않고, CSS의 #section 선택자도 어느 칸을 가리키는지 모호해집니다. 엑셀로 치면, 한 시트 안에서 셀 이름을 같게 세 번 붙인 상황과 같습니다.
<h2 id="carbs">탄수화물</h2>
<h2 id="protein">단백질</h2>
<h2 id="fat">지방</h2>
같은 부류로 묶고 싶다면 id가 아니라 class를 씁니다. id는 한 칸의 고유 이름표라 페이지 안에서 단 하나, class는 같은 부류 표시라 여러 칸에 같이 — 이 두 가지를 같은 자리에 두고 쓰지 않습니다.
오늘 배운 것 체크리스트
-
<table>은 행과 열이 모두 의미를 가지는 자료일 때만 쓴다. -
<thead>·<tbody>·<th>·<td>의 역할을 구분해서 쓸 수 있다. -
<th>에scope="col"또는scope="row"를 붙여 머리글 방향을 표시한다. -
id는 페이지 안에서 단 하나,class는 여러 곳에 붙일 수 있다. -
data-*는 직접 이름을 붙이는 부가 정보,aria-*는 보조기기를 위한 안내다.
자주 묻는 질문
Q. <thead>와 <tbody> 차이가 뭔가요? 짧은 표라면 생략해도 되나요?
A. <thead>는 머리글 줄을 묶고, <tbody>는 그 아래의 본문 줄을 묶습니다. 둘 다 빠뜨려도 표가 그려지긴 하지만, 머리글 줄을 따로 인쇄·고정하거나 본문 줄에만 스타일을 줄 일이 생기면 두 묶음이 있는 편이 훨씬 다루기 쉽습니다. 표가 두세 줄짜리라도 <thead>·<tbody>를 같이 두는 습관을 들이면 표가 커져도 그대로 일관되게 갑니다.
Q. data-* 속성은 어떻게 활용하면 좋을까요?
A. 화면에 직접 보이지는 않지만 그 칸에 묶어 두고 싶은 부가 정보를 담을 때 씁니다. 예를 들어 영양성분표의 한 셀에 data-percent="46"을 적어 두면, 같은 자리에 "46%"라는 글자를 보여 주면서도 숫자 46이라는 값을 따로 꺼내 쓸 길이 열립니다. 이름은 우리가 자유롭게 정하지만, data-로 시작해야 한다는 규칙은 지켜야 합니다.
Q. aria-label은 언제 써야 하나요? 모든 버튼에 다 붙이는 게 좋나요?
A. 시맨틱 태그와 그 안의 글자만으로 의미가 분명하다면 aria-label은 붙이지 않습니다. 예를 들어 <button>닫기</button>처럼 글자가 이미 있다면 충분합니다. 글자가 없는 아이콘 버튼이나 의미가 모호한 영역에 한해서만, 보조기기에게 들려줄 한 줄을 aria-label로 추가합니다.
다음 시간 예고
내일은 CSS 첫걸음 — 인라인, 내부, 외부 스타일시트를 다룹니다. 지금까지 HTML로 표시한 자료에 색·간격·글자 크기를 더할 차례입니다. CSS를 페이지에 끼우는 세 가지 방식의 차이부터 정리합니다.