테이블이 필요한 순간들
웹 개발을 하다 보면 데이터를 정리해서 보여줘야 하는 상황이 자주 발생한다.
사용자 목록, 제품 비교표, 통계 데이터 등... 이럴 때 가장 적합한 HTML 요소가 바로 테이블이다.
CSS Grid나 Flexbox가 대세가 된 지금도, 진짜 표 형태의 데이터를 표현할 때는 테이블만한 게 없다.
HTML 테이블 기본 구조
핵심 태그들의 역할
- <table>: 테이블 전체를 감싸는 컨테이너다
- <tr>: Table Row, 테이블의 행(가로줄)이다
- <th>: Table Header, 헤더 셀(제목 셀)이다
- <td>: Table Data, 데이터 셀(내용 셀)이다
기본 구조 예시
html
<table>
<tr>
<th>이름</th>
<th>나이</th>
<th>직업</th>
</tr>
<tr>
<td>김개발</td>
<td>28</td>
<td>프론트엔드 개발자</td>
</tr>
<tr>
<td>이디자인</td>
<td>26</td>
<td>UI/UX 디자이너</td>
</tr>
</table>
의미론적 테이블 구조
thead, tbody, tfoot 활용
html
<table>
<thead>
<tr>
<th>상품명</th>
<th>가격</th>
<th>재고</th>
</tr>
</thead>
<tbody>
<tr>
<td>맥북 프로</td>
<td>2,590,000원</td>
<td>5개</td>
</tr>
<tr>
<td>아이패드</td>
<td>429,000원</td>
<td>12개</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>총합</td>
<td>3,019,000원</td>
<td>17개</td>
</tr>
</tfoot>
</table>
caption으로 테이블 제목 추가
html
<table>
<caption>2024년 4분기 매출 현황</caption>
<thead>
<tr>
<th>월</th>
<th>매출액</th>
<th>전년 대비</th>
</tr>
</thead>
<tbody>
<tr>
<td>10월</td>
<td>1,250만원</td>
<td>+15%</td>
</tr>
</tbody>
</table>
실무에서 자주 사용하는 패턴들
1. 사용자 관리 테이블
html
<table class="user-table">
<thead>
<tr>
<th>ID</th>
<th>이름</th>
<th>이메일</th>
<th>가입일</th>
<th>상태</th>
<th>관리</th>
</tr>
</thead>
<tbody>
<tr>
<td>001</td>
<td>홍길동</td>
<td>hong@example.com</td>
<td>2024.01.15</td>
<td><span class="status active">활성</span></td>
<td>
<button class="btn-edit">수정</button>
<button class="btn-delete">삭제</button>
</td>
</tr>
</tbody>
</table>
2. 제품 비교표
html
<table class="comparison-table">
<thead>
<tr>
<th>기능</th>
<th>기본 플랜</th>
<th>프로 플랜</th>
<th>엔터프라이즈</th>
</tr>
</thead>
<tbody>
<tr>
<td>사용자 수</td>
<td>최대 5명</td>
<td>최대 50명</td>
<td>무제한</td>
</tr>
<tr>
<td>저장 공간</td>
<td>10GB</td>
<td>100GB</td>
<td>1TB</td>
</tr>
<tr>
<td>가격</td>
<td>무료</td>
<td>월 29,000원</td>
<td>월 99,000원</td>
</tr>
</tbody>
</table>
3. 대시보드 통계표
html
<table class="dashboard-stats">
<thead>
<tr>
<th>구분</th>
<th>오늘</th>
<th>어제</th>
<th>증감률</th>
</tr>
</thead>
<tbody>
<tr>
<td>방문자</td>
<td>1,247명</td>
<td>1,156명</td>
<td class="increase">+7.9%</td>
</tr>
<tr>
<td>페이지뷰</td>
<td>3,891회</td>
<td>4,012회</td>
<td class="decrease">-3.0%</td>
</tr>
</tbody>
</table>
colspan과 rowspan으로 셀 병합하기
colspan: 가로 셀 병합
html
<table>
<tr>
<th colspan="3">2024년 분기별 실적</th>
</tr>
<tr>
<th>1분기</th>
<th>2분기</th>
<th>3분기</th>
</tr>
<tr>
<td>1,200만원</td>
<td>1,450만원</td>
<td>1,380만원</td>
</tr>
</table>
rowspan: 세로 셀 병합
html
<table>
<tr>
<th rowspan="2">지역</th>
<th colspan="2">매출</th>
</tr>
<tr>
<th>온라인</th>
<th>오프라인</th>
</tr>
<tr>
<td>서울</td>
<td>2,300만원</td>
<td>1,800만원</td>
</tr>
<tr>
<td>부산</td>
<td>1,200만원</td>
<td>900만원</td>
</tr>
</table>
현대적인 테이블 스타일링
기본 테이블 스타일
css
table {
width: 100%;
border-collapse: collapse;
margin: 2rem 0;
font-size: 0.9rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f8f9fa;
font-weight: 600;
color: #333;
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
}
tr:hover {
background-color: #f5f5f5;
}
반응형 테이블
css
.table-container {
overflow-x: auto;
margin: 1rem 0;
}
@media (max-width: 768px) {
table {
font-size: 0.8rem;
}
th, td {
padding: 8px 6px;
}
/* 모바일에서 일부 열 숨기기 */
.hide-mobile {
display: none;
}
}
스트라이프 테이블
css
.stripe-table tbody tr:nth-child(even) {
background-color: #f8f9fa;
}
.stripe-table tbody tr:nth-child(odd) {
background-color: white;
}
모던 카드 스타일 테이블
css
.card-table {
border: none;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.card-table th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 20px 15px;
}
.card-table td {
border: none;
padding: 15px;
border-bottom: 1px solid #eee;
}
.card-table tbody tr:last-child td {
border-bottom: none;
}
접근성을 고려한 테이블
scope 속성으로 헤더-데이터 관계 명시
html
<table>
<thead>
<tr>
<th scope="col">이름</th>
<th scope="col">수학</th>
<th scope="col">영어</th>
<th scope="col">평균</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">김학생</th>
<td>90</td>
<td>85</td>
<td>87.5</td>
</tr>
</tbody>
</table>
headers 속성으로 복잡한 테이블 접근성 개선
html
<table>
<tr>
<th id="name">이름</th>
<th id="math">수학</th>
<th id="eng">영어</th>
</tr>
<tr>
<td headers="name">홍길동</td>
<td headers="math">95</td>
<td headers="eng">88</td>
</tr>
</table>
자바스크립트로 테이블 기능 확장
정렬 기능 구현
javascript
function sortTable(columnIndex) {
const table = document.querySelector('table');
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.rows);
const sortedRows = rows.sort((a, b) => {
const aText = a.cells[columnIndex].textContent.trim();
const bText = b.cells[columnIndex].textContent.trim();
// 숫자인지 확인
if (!isNaN(aText) && !isNaN(bText)) {
return Number(aText) - Number(bText);
}
return aText.localeCompare(bText);
});
// 기존 행들 제거하고 정렬된 행들 추가
tbody.innerHTML = '';
sortedRows.forEach(row => tbody.appendChild(row));
}
// 헤더 클릭 시 정렬
document.querySelectorAll('th').forEach((header, index) => {
header.addEventListener('click', () => sortTable(index));
header.style.cursor = 'pointer';
});
검색 필터 기능
javascript
function filterTable(searchTerm) {
const table = document.querySelector('table');
const rows = table.querySelectorAll('tbody tr');
rows.forEach(row => {
const text = row.textContent.toLowerCase();
const shouldShow = text.includes(searchTerm.toLowerCase());
row.style.display = shouldShow ? '' : 'none';
});
}
// 검색 입력 필드 연결
document.getElementById('search').addEventListener('input', (e) => {
filterTable(e.target.value);
});
행 선택 기능
javascript
document.querySelectorAll('tbody tr').forEach(row => {
row.addEventListener('click', function() {
// 기존 선택 해제
document.querySelectorAll('tbody tr.selected').forEach(r => {
r.classList.remove('selected');
});
// 현재 행 선택
this.classList.add('selected');
});
});
반응형 테이블 고급 기법
모바일에서 카드 형태로 변환
css
@media (max-width: 768px) {
.responsive-table thead {
display: none;
}
.responsive-table tbody,
.responsive-table tr,
.responsive-table td {
display: block;
}
.responsive-table tr {
border: 1px solid #ccc;
margin-bottom: 10px;
padding: 10px;
border-radius: 8px;
}
.responsive-table td {
border: none;
position: relative;
padding-left: 50%;
}
.responsive-table td:before {
content: attr(data-label) ": ";
position: absolute;
left: 6px;
width: 45%;
font-weight: bold;
}
}
html
<table class="responsive-table">
<thead>
<tr>
<th>이름</th>
<th>이메일</th>
<th>전화번호</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="이름">김개발</td>
<td data-label="이메일">kim@example.com</td>
<td data-label="전화번호">010-1234-5678</td>
</tr>
</tbody>
</table>
성능 최적화 팁
큰 테이블의 가상 스크롤링
javascript
class VirtualTable {
constructor(container, data, rowHeight = 50) {
this.container = container;
this.data = data;
this.rowHeight = rowHeight;
this.visibleRows = Math.ceil(container.clientHeight / rowHeight) + 5;
this.init();
}
init() {
this.container.innerHTML = `
<div class="virtual-scroll" style="height: ${this.data.length * this.rowHeight}px">
<table class="virtual-table"></table>
</div>
`;
this.virtualScroll = this.container.querySelector('.virtual-scroll');
this.table = this.container.querySelector('.virtual-table');
this.container.addEventListener('scroll', () => this.updateVisibleRows());
this.updateVisibleRows();
}
updateVisibleRows() {
const scrollTop = this.container.scrollTop;
const startIndex = Math.floor(scrollTop / this.rowHeight);
const endIndex = Math.min(startIndex + this.visibleRows, this.data.length);
this.renderRows(startIndex, endIndex);
this.table.style.transform = `translateY(${startIndex * this.rowHeight}px)`;
}
renderRows(start, end) {
const rows = this.data.slice(start, end).map(item => `
<tr>
<td>${item.name}</td>
<td>${item.email}</td>
<td>${item.phone}</td>
</tr>
`).join('');
this.table.innerHTML = `
<thead>
<tr><th>이름</th><th>이메일</th><th>전화번호</th></tr>
</thead>
<tbody>${rows}</tbody>
`;
}
}
주의사항과 베스트 프랙티스
레이아웃 용도로 테이블 사용 금지
html
<!-- 잘못된 사용 - 레이아웃 목적 -->
<table>
<tr>
<td>사이드바</td>
<td>메인 컨텐츠</td>
</tr>
</table>
<!-- 올바른 사용 - 표 형태 데이터 -->
<table>
<tr>
<th>상품명</th>
<th>가격</th>
</tr>
<tr>
<td>노트북</td>
<td>1,200,000원</td>
</tr>
</table>
빈 셀 처리
html
<!-- 빈 셀에는 또는 - 사용 -->
<table>
<tr>
<td>데이터 있음</td>
<td> </td><!-- 빈 셀 -->
<td>-</td><!-- 해당 없음을 나타내는 셀 -->
</tr>
</table>
테이블 캡션과 요약 정보
html
<table>
<caption>
2024년 월별 매출 현황
<span class="table-summary">총 12개월 데이터, 단위: 만원</span>
</caption>
<!-- ... -->
</table>
마무리: 테이블의 올바른 활용
테이블은 단순해 보이지만 올바르게 사용하면 강력한 도구가 된다.
div와 CSS로 모든 걸 해결하려 하지 말고, 진짜 표 형태의 데이터를 다룰 때는 테이블을 사용하는 것이 의미론적으로도, 접근성 측면에서도 훨씬 좋다.
특히 관리자 페이지나 대시보드 같은 데이터 중심의 인터페이스를 만들 때는 테이블의 진가가 발휘된다. 정렬, 필터링, 페이지네이션 등의 기능을 추가하면 사용자 경험도 크게 향상시킬 수 있다.
테이블을 단순히 '구식 기술'로 생각하지 말고, 적재적소에 활용할 수 있는 개발자가 되자.
테이블과 함께 HTML의 구조적 데이터 표현을 담당하는 또 다른 요소들도 있다.
용어와 설명을 체계적으로 정리할 때 유용한 HTML dt, dd, dl 태그 완전 정복 - 레거시 코드에서 발견한 숨겨진 보석 글도 정리했다.
FAQ나 용어사전을 만들 때 테이블보다 더 적합한 선택이 될 수 있다.
'어쩌다풀스택구스' 카테고리의 다른 글
| HTML dt, dd, dl 태그 완전 정복_ 레거시 코드에서 발견한 숨겨진 보석 (0) | 2026.08.03 |
|---|---|
| 윈도우 환경 신입 개발자 필수 설정_ 바탕화면 작업 폴더를 D드라이브로 옮겨야 하는 이유 (0) | 2026.08.02 |
| [잡글]카카오 데이터 센터 화재 당시 'Daum 다음' 메인 페이지 코드까봄 (0) | 2023.01.07 |













