1. 표는 언제 써야 하나
1.1 올바른 용도: 표 형식 데이터(Tabular Data)
<table> 은 행(row)과 열(column)의 관계가 의미 있는 데이터를 표현할 때 쓴다.
- 스프레드시트처럼 행과 열이 교차하는 데이터
- 비교표 (기능 A vs B vs C)
- 시간표, 일정표
- 통계 데이터
1.2 잘못된 용도: 레이아웃
1990년대~2000년대 초반엔 페이지 전체 레이아웃을 <table> 로 짜는 것이 일반적이었다. 현재는 레이아웃 목적의 <table> 사용은 잘못된 관행이다.
- 스크린 리더가 표 데이터로 인식해 "4개 열, 20개 행의 표"처럼 잘못 안내한다.
- 반응형 웹을 만들기 극히 어렵다.
- 레이아웃은 CSS Flexbox 또는 Grid로 구현한다.
2. 표의 기본 구조
2.1 전체 골격
<table>
<caption>월별 스터디 현황</caption>
<colgroup>
<col>
<col span="2" class="highlight-col">
</colgroup>
<thead>
<tr>
<th scope="col">월</th>
<th scope="col">공부 시간</th>
<th scope="col">완료 챕터</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">6월</th>
<td>42시간</td>
<td>9편</td>
</tr>
<tr>
<th scope="row">7월</th>
<td>38시간</td>
<td>11편</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">합계</th>
<td>80시간</td>
<td>-</td>
</tr>
</tfoot>
</table>
3. <table> 의 구성 요소들
3.1 <caption>: 표의 제목
<table>
<caption>2026년 상반기 스터디 현황</caption>
...
</table>
<caption>은<table>안에서 가장 먼저 와야 한다.<thead>보다 앞에 위치한다.- 기본적으로 표 위에 표시되며, CSS
caption-side: bottom으로 아래로 옮길 수 있다. - 스크린 리더는 표를 읽기 전에
<caption>을 먼저 읽어 표의 맥락을 안내한다. - HTML5에서
<table>의summary속성은 폐기(deprecated) 됐다. 표에 대한 설명이 필요하면<caption>또는aria-describedby를 사용한다.
3.2 <thead>: 머리글 영역
- 표의 열 제목 행을 담는 그룹이다.
- 긴 표를 인쇄할 때 각 페이지마다 머리글이 반복되도록 브라우저가 처리한다.
- 안에
<tr>과<th>요소를 담는다.
3.3 <tbody>: 본문 데이터 영역
- 표의 실제 데이터 행을 담는 그룹이다.
- 생략해도 브라우저가 자동으로 추가하지만, 명시적으로 쓰는 것이 좋다.
<table>안에 여러<tbody>를 써서 데이터를 논리적으로 그룹화할 수 있다.
3.4 <tfoot>: 바닥글 영역
- 합계, 평균, 총계처럼 표의 요약·결론 행을 담는 그룹이다.
<tfoot>은 반드시<tbody>뒤에 위치해야 한다. HTML4에서는<tbody>앞에 두는 것이 허용됐지만, HTML5 Living Standard는<tfoot>이<tbody>다음에 와야 한다고 규정한다.
<table>
<caption>표 제목</caption> ← 1순위
<colgroup>...</colgroup> ← 2순위 (선택)
<thead>...</thead> ← 3순위 (선택)
<tbody>...</tbody> ← 4순위
<tfoot>...</tfoot> ← 5순위 (선택, tbody 뒤에)
</table>
3.5 <colgroup> 과 <col>: 열 스타일 지정
특정 열 전체에 CSS를 적용할 때 쓴다. <col> 요소를 열 순서대로 배치한다.
<table>
<colgroup>
<col> <!-- 1열: 스타일 없음 -->
<col class="highlight-col"> <!-- 2열 -->
<col span="2" class="data-cols"> <!-- 3~4열: 한 번에 지정 -->
</colgroup>
...
</table>
<col> 에는 CSS의 일부 속성만 적용된다: border, background, width, visibility. 대부분의 다른 CSS 속성은 <col> 을 통해 열 전체에 적용하는 것이 지원되지 않는다.
4. <tr>, <th>, <td>: 행과 셀
4.1 <tr>: 테이블 행(Row)
<tbody>
<tr>
<td>1행 1열</td>
<td>1행 2열</td>
</tr>
<tr>
<td>2행 1열</td>
<td>2행 2열</td>
</tr>
</tbody>
<tr> 은 반드시 <thead>, <tbody>, <tfoot> 안에 위치해야 한다.
4.2 <th>: 헤더 셀 vs <td>: 데이터 셀
<th> | <td> | |
|---|---|---|
| 의미 | 헤더 셀 (제목·레이블) | 데이터 셀 |
| 기본 스타일 | 굵게(bold), 가운데 정렬 | 보통 굵기, 왼쪽 정렬 |
| 스크린 리더 | 데이터 셀의 문맥 정보로 읽힘 | <th> 를 참조해 읽힘 |
<th> 는 <thead> 안에서 열 제목으로만 쓰이는 것이 아니라, <tbody> 의 각 행에서 행 제목(row header) 으로도 쓸 수 있다.
4.3 scope 속성: 헤더 셀의 적용 범위
scope 속성은 <th> 가 어느 방향의 셀들에 대한 헤더인지 명시한다. 스크린 리더가 데이터 셀을 읽을 때 어느 헤더에 속하는지 올바르게 안내하기 위한 접근성 속성이다.
scope 값 | 의미 |
|---|---|
col | 이 <th> 는 같은 열의 데이터 셀들에 대한 헤더 |
row | 이 <th> 는 같은 행의 데이터 셀들에 대한 헤더 |
colgroup | 이 <th> 는 <colgroup> 으로 묶인 열 그룹의 헤더 |
rowgroup | 이 <th> 는 <thead>·<tbody>·<tfoot> 행 그룹의 헤더 |
<table>
<thead>
<tr>
<th scope="col">과목</th> <!-- 열 헤더 -->
<th scope="col">점수</th>
<th scope="col">등급</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">HTML</th> <!-- 행 헤더 -->
<td>95</td>
<td>A</td>
</tr>
<tr>
<th scope="row">CSS</th> <!-- 행 헤더 -->
<td>88</td>
<td>B+</td>
</tr>
</tbody>
</table>
스크린 리더는 "88" 셀을 읽을 때 "CSS 점수 88"처럼 행 헤더와 열 헤더를 함께 안내한다.
5. colspan 과 rowspan: 셀 병합
5.1 colspan: 열 방향 병합
colspan="n" 은 해당 셀이 가로로 n개의 열을 차지하게 한다.
<table>
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col" colspan="2">연락처</th> <!-- 2열 병합 -->
</tr>
<tr>
<th scope="col">-</th>
<th scope="col">전화</th>
<th scope="col">이메일</th>
</tr>
</thead>
<tbody>
<tr>
<td>장유주</td>
<td>010-0000-0000</td>
<td>uzu@example.com</td>
</tr>
</tbody>
</table>
렌더링 결과:
┌────────┬──────────────────────────────┐
│ 이름 │ 연락처 │ ← colspan=2
├────────┼──────────────┬───────────────┤
│ - │ 전화 │ 이메일 │
├────────┼──────────────┼───────────────┤
│ 장유주 │010-0000-0000 │uzu@example.com│
└────────┴──────────────┴───────────────┘
5.2 rowspan: 행 방향 병합
rowspan="n" 은 해당 셀이 세로로 n개의 행을 차지하게 한다.
<table>
<tbody>
<tr>
<th scope="rowgroup" rowspan="2">오전</th> <!-- 2행 병합 -->
<td>09:00 HTML 기초</td>
</tr>
<tr>
<!-- "오전" 셀이 이미 이 행을 차지하므로 <td>를 하나 쓰지 않는다 -->
<td>10:00 CSS 기초</td>
</tr>
<tr>
<th scope="rowgroup" rowspan="2">오후</th>
<td>13:00 JavaScript</td>
</tr>
<tr>
<td>14:00 프로젝트</td>
</tr>
</tbody>
</table>
rowspan·colspan사용 시 핵심 규칙셀이 n행 또는 n열을 차지하면, 이후 해당 위치의
<tr>에서는 그만큼<td>·<th>를 하나 줄여서 써야 한다. 빠진 셀의 자리는 앞 행의 병합 셀이 차지하고 있기 때문이다. 이 규칙을 어기면 열 수가 맞지 않아 표가 틀어진다.
5.3 colspan 과 rowspan 동시 사용
두 속성을 함께 쓰면 셀이 직사각형 블록을 차지하게 할 수 있다.
<td colspan="3" rowspan="2">
이 셀은 3열 × 2행 영역을 차지한다.
</td>
6. 표 꾸미기 기초 (CSS)
6.1 border-collapse
표 테두리 처리 방식을 결정하는 가장 기본적인 CSS 속성이다.
/* separate(기본값): 각 셀의 테두리가 분리됨 → 이중 선처럼 보임 */
table { border-collapse: separate; }
/* collapse: 인접한 테두리가 하나로 합쳐짐 → 깔끔한 단일 선 */
table { border-collapse: collapse; }
실무에서는 대부분 border-collapse: collapse 를 기본으로 쓴다.
6.2 줄무늬(Zebra Striping)
:nth-child() 가상 클래스로 홀수·짝수 행에 교대로 배경색을 넣어 가독성을 높인다.
tbody tr:nth-child(even) {
background-color: #f9fafb;
}
tbody tr:hover {
background-color: #eff6ff;
}
6.3 table-layout
/* auto(기본값): 셀 내용에 따라 열 너비 자동 계산 (렌더링 느림) */
table { table-layout: auto; }
/* fixed: 첫 행의 너비 또는 col 설정에 따라 고정 (렌더링 빠름) */
table {
table-layout: fixed;
width: 100%; /* fixed에서는 table 너비 명시가 중요 */
}
table-layout: fixed 는 표가 크거나 데이터가 많을 때 렌더링 성능을 개선한다. 전체 내용을 다 읽지 않고도 열 너비를 바로 계산할 수 있기 때문이다.
6.4 caption-side
/* top(기본값): caption이 표 위에 */
table { caption-side: top; }
/* bottom: caption이 표 아래에 */
table { caption-side: bottom; }
6.5 실전 표 기본 스타일
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
caption {
caption-side: top;
text-align: left;
font-weight: bold;
margin-bottom: 0.5rem;
}
th, td {
padding: 0.75rem 1rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
thead th {
background-color: #f3f4f6;
font-weight: 600;
}
tbody tr:nth-child(even) {
background-color: #f9fafb;
}
tbody tr:hover {
background-color: #eff6ff;
}
7. [실습] 셀 병합으로 스터디 시간표 만들기
7.1 완성 목표
colspan 과 rowspan 을 모두 활용한 주간 스터디 시간표를 만든다.
┌───────────┬───────────┬───────────┬───────────┬───────────┬───────────┐
│ 시간 │ 월요일 │ 화요일 │ 수요일 │ 목요일 │ 금요일 │
├───────────┼───────────┴───────────┼───────────┴───────────┼───────────┤
│09:00~11:00│ HTML 기초 (×2) │ CSS 기초 (×2) │ JS 기초 │
│ │ colspan=2, rowspan=2 │ colspan=2, rowspan=2 │ rowspan=2 │
│10:00~11:00│ (병합됨) │ (병합됨) │ (병합됨) │
├───────────┼───────────────────────────────────────────────────────────┤
│11:00~13:00│ 점심 · 휴식 (colspan=5) │
├───────────┼───────────────────────────────┬───────────────────────────┤
│13:00~14:00│ 프로젝트 실습 (colspan=3) │ 복습 & Q&A (colspan=2) │
│ │ rowspan=2 │ rowspan=2 │
│14:00~15:00│ (병합됨) │ (병합됨) │
└───────────┴───────────────────────────────┴───────────────────────────┘
7.2 병합 설계 분석
각 행을 만들기 전에 어떤 셀이 어디서 시작하고 얼마나 차지하는지 미리 파악한다.
| 행 | 시간 열 | 월 | 화 | 수 | 목 | 금 |
|---|---|---|---|---|---|---|
| 1행 | 09:00~10:00 | HTML 기초 colspan=2 rowspan=2 | ← (병합) | CSS 기초 colspan=2 rowspan=2 | ← (병합) | JS 기초 rowspan=2 |
| 2행 | 10:00~11:00 | ↑(병합) | - | ↑(병합) | - | ↑(병합) |
| 3행 | 11:00~13:00 | 점심·휴식 colspan=5 | - | - | - | - |
| 4행 | 13:00~14:00 | 프로젝트 실습 colspan=3 rowspan=2 | - | - | 복습 & Q&A colspan=2 rowspan=2 | - |
| 5행 | 14:00~15:00 | ↑(병합) | - | - | ↑(병합) | - |
7.3 완성 코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>스터디 시간표</title>
<style>
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
caption {
caption-side: top;
font-size: 1.2rem;
font-weight: bold;
text-align: left;
margin-bottom: 0.75rem;
}
th, td {
padding: 0.75rem 1rem;
border: 1px solid #d1d5db;
text-align: center;
vertical-align: middle;
}
thead th {
background-color: #1e3a5f;
color: white;
}
/* 시간 열 */
tbody th {
background-color: #f3f4f6;
font-weight: 600;
white-space: nowrap;
}
/* 과목별 색상 */
.html { background-color: #dbeafe; }
.css { background-color: #d1fae5; }
.js { background-color: #fef3c7; }
.lunch { background-color: #f5f5f5; color: #6b7280; }
.project { background-color: #ede9fe; }
.review { background-color: #fce7f3; }
</style>
</head>
<body>
<table>
<caption>2026년 7월 스터디 시간표</caption>
<colgroup>
<col style="width: 14%"> <!-- 시간 열 -->
<col style="width: 17.2%"> <!-- 월 -->
<col style="width: 17.2%"> <!-- 화 -->
<col style="width: 17.2%"> <!-- 수 -->
<col style="width: 17.2%"> <!-- 목 -->
<col style="width: 17.2%"> <!-- 금 -->
</colgroup>
<thead>
<tr>
<th scope="col">시간</th>
<th scope="col">월요일</th>
<th scope="col">화요일</th>
<th scope="col">수요일</th>
<th scope="col">목요일</th>
<th scope="col">금요일</th>
</tr>
</thead>
<tbody>
<!-- 1행: 09:00~10:00 -->
<!-- HTML은 월·화 2열 + 2행 병합 / CSS는 수·목 2열 + 2행 병합 / JS는 2행 병합 -->
<tr>
<th scope="row">09:00~10:00</th>
<td class="html" colspan="2" rowspan="2">
<strong>HTML 기초</strong><br>
<small>문서 구조 · 시맨틱 태그</small>
</td>
<td class="css" colspan="2" rowspan="2">
<strong>CSS 기초</strong><br>
<small>선택자 · 박스 모델</small>
</td>
<td class="js" rowspan="2">
<strong>JS 기초</strong><br>
<small>변수 · 함수</small>
</td>
</tr>
<!-- 2행: 10:00~11:00 -->
<!-- 앞 행의 colspan=2 rowspan=2 · rowspan=2 셀들이 이 행을 차지함 -->
<!-- th(시간)만 새로 쓰고 나머지 td는 생략 -->
<tr>
<th scope="row">10:00~11:00</th>
<!-- 5개 열 모두 rowspan으로 병합됨 → td 없음 -->
</tr>
<!-- 3행: 점심·휴식 (5열 전체 병합) -->
<tr>
<th scope="row">11:00~13:00</th>
<td class="lunch" colspan="5">🍱 점심 · 휴식</td>
</tr>
<!-- 4행: 13:00~14:00 -->
<!-- 프로젝트: 월·화·수 3열 + 2행 병합 / 복습: 목·금 2열 + 2행 병합 -->
<tr>
<th scope="row">13:00~14:00</th>
<td class="project" colspan="3" rowspan="2">
<strong>팀 프로젝트 실습</strong><br>
<small>HTML + CSS 결합</small>
</td>
<td class="review" colspan="2" rowspan="2">
<strong>복습 & Q&A</strong><br>
<small>오류 수정 · 질의응답</small>
</td>
</tr>
<!-- 5행: 14:00~15:00 -->
<!-- 앞 행의 colspan·rowspan 셀들이 이 행을 차지함 -->
<tr>
<th scope="row">14:00~15:00</th>
<!-- 5개 열 모두 rowspan으로 병합됨 → td 없음 -->
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">일일 합계</th>
<td colspan="5">총 5시간 (오전 2h + 오후 2h)</td>
</tr>
</tfoot>
</table>
</body>
</html>
7.4 코드 핵심 포인트
- 병합된 셀의 자리에는
<td>를 쓰지 않는다. 2행과 5행에서<th scope="row">만 쓰고<td>를 0개 쓴 이유다. colspan+rowspan동시 사용 시 그 셀이 차지하는 직사각형 영역 전체를 이후 행에서 비워야 한다.&는&의 HTML 엔티티. "복습 & Q&A" 처럼&기호를 HTML 안에 직접 쓸 때는 인코딩한다.<colgroup>+<col>으로 각 열의 너비를 미리 지정해table-layout: fixed와 함께 렌더링 성능을 높였다.