HTML 비교표의 caption과 scope를 지정하고 모바일 넘침을 어떻게 처리하나요?
HTML 표는 요금제처럼 행과 열의 관계를 비교하는 정보에 씁니다. caption으로 제목을 붙이고 th의 scope로 행·열 제목을 구분합니다. 모바일에서는 표를 감싼 영역만 가로로 스크롤합니다.
적용 조건·범위: 키보드 사용 환경에서 wrapper 자체 focus가 필요한 경우 tabindex="0" 적용을 검토할 수 있지만, 불필요한 focus stop을 늘리지 않도록 실제 브라우저 접근성 동작을 확인합니다.
공식 문서 참고: W3C WAI: th와 scope로 표 제목·데이터 연결 · W3C WAI: caption으로 표 내용을 식별하기
독립 실습: 직접 만들고 확인하기
HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기 · 독립 실습
좁은 폭에서 표 영역만 좌우로 스크롤하고 행·열 제목을 확인하세요.
예제 크게 보기 (새 탭).table-scroll { max-width: 100%; overflow-x: auto; }
.table-scroll:focus-visible { outline: 2px solid #222; }
.table-scroll table { width: 100%; min-width: 540px; border-collapse: collapse; }
.table-scroll th, .table-scroll td { padding: 12px; border: 1px solid #777; text-align: left; }직접 실행: 환경 준비와 전체 파일
독자 질문
요금제 비교표의 행·열 관계를 유지하면서 작은 화면에서는 표만 어떻게 스크롤할까요?
직접 만들 화면
모바일에서 표 영역을 좌우로 움직여도 페이지 본문은 옆으로 밀리지 않는 요금제 비교 화면
새 폴더에 index.html과 style.css를 만들고 아래 전체 파일을 각각 저장하세요. 브라우저에서 index.html을 엽니다.
<!doctype html>
<html lang="ko">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기</title><link rel="stylesheet" href="./style.css"></head>
<body>
<section aria-labelledby="pricing-title">
<h2 id="pricing-title">요금제</h2>
<p id="table-hint">표를 좌우로 스크롤할 수 있습니다.</p>
<div class="table-scroll" role="region" aria-labelledby="pricing-title" aria-describedby="table-hint" tabindex="0">
<table>
<caption>TaskFlow 요금제 비교</caption>
<thead><tr><th scope="col">항목</th><th scope="col">Basic</th><th scope="col">Pro</th></tr></thead>
<tbody>
<tr><th scope="row">프로젝트</th><td>3개</td><td>무제한</td></tr>
<tr><th scope="row">작업 보관</th><td>30일</td><td>무제한</td></tr>
</tbody>
</table>
</div>
</section>
</body>
</html>
확인할 결과와 실제 검증 상태
360px에서 표 내부만 좌우로 이동하고 문서 전체에는 가로 스크롤이 없어야 합니다. Tab으로 스크롤 영역에 초점이 보이는지 확인하고 행 제목과 열 제목을 읽어 보세요. 표의 caption과 th의 scope는 시각 배치가 아니라 데이터 관계를 설명합니다.
실제 브라우저·DevTools 조작 결과: 미확인. 위 내용은 코드에서 예상되는 결과와 독자가 수행할 확인 절차입니다.
다음 학습 연결
09회차에서는 이 화면에 실제 이미지를 추가할 때 경로와 비율, 레이아웃 이동을 확인합니다.
기본 table
<table>
<caption>
요금제 비교
</caption>
<thead>
<tr>
<th scope="col">
항목
</th>
<th scope="col">
Basic
</th>
<th scope="col">
Pro
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
프로젝트 수
</th>
<td>3개</td>
<td>무제한</td>
</tr>
</tbody>
</table>
caption
표가 무엇을 비교하는지 설명합니다.
단순 시각 제목과 달리 table과 직접 연결된 제목 역할을 합니다.
scope
열 header:
<th scope="col">
Basic
</th>
행 header:
<th scope="row">
프로젝트 수
</th>
스크린리더가 data cell과 header 관계를 이해하는 데 도움이 됩니다.
모바일 문제
열이 많으면 작은 화면에서 표가 viewport보다 넓어질 수 있습니다.
페이지 전체에 overflow-x:auto를 주지 않습니다.
<div class="table-scroll">
<table>
...
</table>
</div>
.table-scroll {
overflow-x: auto;
}
.table-scroll table {
min-width: 640px;
}
표 영역만 가로로 움직입니다.

스크롤 가능성 표시
스크롤 영역이 있다는 것을 사용자가 알 수 있도록 충분한 내용 노출과 문맥을 제공합니다.
키보드 사용 환경에서 wrapper 자체 focus가 필요한 경우 tabindex="0" 적용을 검토할 수 있지만, 불필요한 focus stop을 늘리지 않도록 실제 브라우저 접근성 동작을 확인합니다.
table을 div로 바꿔야 하는 경우
데이터 관계가 아니라 단순 카드 목록이라면 table이 필요하지 않습니다.
행/열 비교 데이터
→ table
각 항목이 독립 카드
→ article/list/Grid
자주 발생하는 오류
caption을 CSS로 숨기고 별도 h2만 씁니다
화면 디자인 요구가 있다면 caption을 visually-hidden 처리할 수도 있지만 표 자체 이름이 사라지지 않도록 합니다.
td를 전부 div처럼 스타일링합니다
시맨틱 관계를 유지한 채 CSS를 적용할 수 있습니다.
학습 완료 체크리스트
- table을 데이터 비교에 사용할 수 있습니다.
- caption을 작성할 수 있습니다.
- th와 scope를 사용할 수 있습니다.
- 모바일에서 wrapper만 overflow시킬 수 있습니다.
- page 전체 horizontal scroll을 만들지 않을 수 있습니다.
독립 실습은 여기서 마칠 수 있습니다. 아래 선택 실습은 TaskFlow 프로젝트 파일로 진행합니다.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 간단한 서비스 화면에서 시작해 할 일 앱으로 이어 만드는 예제입니다. 이 선택 실습에서는 HTML과 CSS로 이번 화면을 만듭니다.
TaskFlow 요금제를 비교하는 접근 가능한 표를 추가합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
HTML 표 만들기: caption·scope로 비교표를 읽기 쉽게, 모바일에서는 표만 스크롤하기 · 단계별 TaskFlow
TaskFlow 누적 8단계입니다. 좁은 폭에서 표 영역만 좌우로 스크롤하고 행·열 제목을 확인하세요.
예제 크게 보기 (새 탭)실행 가능한 시작본과 이번 단계
이번 단계 시작 ZIP을 새 폴더에 압축 해제합니다. 이 ZIP은 직전 단계의 완료 파일과 같으며 앞 글의 코드를 따로 모을 필요가 없습니다. 이번 단계 완료 ZIP은 다른 폴더에 풀어 비교합니다.
기존 요금제 카드는 유지하고 그 아래 caption·scope가 있는 표를 추가합니다. 스크롤 wrapper는 키보드 초점을 받을 수 있게 합니다.
index.html: 이 파일의 기존 내용을 아래 완료 파일 전체와 대조하여 교체합니다. 다른 파일은 유지합니다.style.css: 이 파일의 기존 내용을 아래 완료 파일 전체와 대조하여 교체합니다. 다른 파일은 유지합니다.
짧은 코드 예제는 변경 위치를 설명합니다. 파일 전체를 작성하거나 교체할 때는 완료 파일 전체를 사용하세요. 생략 표시가 있는 부분 코드를 파일 전체로 저장하지 않습니다.
실행: ZIP의 index.html을 브라우저에서 엽니다. 외부 서버나 npm 설치 없이 로컬 HTML·CSS·이미지를 사용합니다. 문의 폼은 서버 전송을 연결하지 않은 문서 예제입니다. 연습 값만 사용하고 입력 형식을 확인합니다.
직접 확인할 결과: 표의 세 기능 행과 열 제목을 확인하고 좁은 폭에서는 표 영역만 가로로 이동합니다.
전체 파일·코드 보기
완료 파일 전체
index.html 전체 파일
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>
TaskFlow
</title>
<link href="./style.css" rel="stylesheet"/>
</head>
<body>
<header class="site-header">
<div class="page site-header__inner">
<a class="brand" href="#home">
TaskFlow
</a>
<nav aria-label="주요 메뉴" class="nav">
<a href="#features">
기능
</a>
<a href="#contact">
문의
</a>
</nav>
</div>
</header>
<main class="page" id="home">
<section>
<h1>
해야 할 일을 한눈에 정리하세요
</h1>
<p>
TaskFlow는 오늘의 작업을 빠르게 정리하는 서비스입니다.
</p>
</section>
<section id="features">
<h2>
주요 기능
</h2>
<div class="feature-grid">
<article class="card feature-card">
<span class="feature-card__badge">
NEW
</span>
<h3>
빠른 작업 추가
</h3>
<p>
해야 할 일을 바로 기록할 수 있습니다.
</p>
</article>
<article class="card">
<h3>
간단한 분류
</h3>
<p>
해야 할 일을 구분합니다.
</p>
</article>
<article class="card">
<h3>
진행 확인
</h3>
<p>
완료 여부를 확인합니다.
</p>
</article>
<article class="card task-preview">
<div class="task-preview__content">
<h3>
아주 긴 작업 제목과 URL
</h3>
<p>
https://example.com/very-long-task-url-without-normal-break-points-123456789
</p>
</div>
<a href="#contact">
문의
</a>
</article>
</div>
</section>
<section class="faq" id="faq">
<h2>
자주 묻는 질문
</h2>
<details>
<summary>
TaskFlow는 어떤 서비스인가요?
</summary>
<p>
해야 할 일을 빠르게 정리하는 작업 관리 서비스 예제입니다.
</p>
</details>
<details>
<summary>
모바일에서도 볼 수 있나요?
</summary>
<p>
작은 화면부터 설계한 반응형 레이아웃을 사용합니다.
</p>
</details>
<details>
<summary>
문의는 어디서 하나요?
</summary>
<p>
아래 문의 폼을 이용할 수 있습니다.
</p>
</details>
</section>
<section id="pricing">
<h2>
요금제
</h2>
<div class="pricing-grid">
<article class="card">
<h3>
Basic
</h3>
<p>
개인 학습용
</p>
</article>
<article class="card">
<h3>
Pro
</h3>
<p>
더 많은 프로젝트
</p>
</article>
</div>
<div aria-label="요금제 비교표 가로 스크롤" class="table-scroll" role="region" tabindex="0">
<table class="pricing-table">
<caption>
TaskFlow 요금제 기능 비교
</caption>
<thead>
<tr>
<th scope="col">
항목
</th>
<th scope="col">
Basic
</th>
<th scope="col">
Pro
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
프로젝트
</th>
<td>
3개
</td>
<td>
무제한
</td>
</tr>
<tr>
<th scope="row">
작업 보관
</th>
<td>
30일
</td>
<td>
무제한
</td>
</tr>
<tr>
<th scope="row">
팀 공유
</th>
<td>
없음
</td>
<td>
지원
</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="contact">
<h2>
문의하기
</h2>
<form>
<div class="form-field">
<label for="name">
이름
</label>
<input autocomplete="name" id="name" name="name" type="text"/>
</div>
<div class="form-field">
<label for="email">
이메일
</label>
<input autocomplete="email" id="email" name="email" type="email"/>
</div>
<div class="form-field">
<label for="message">
문의 내용
</label>
<textarea id="message" name="message" rows="5"></textarea>
</div>
<button type="submit">
문의 보내기
</button>
</form>
</section>
</main>
<footer class="site-footer">
<div class="page">
<p>
© TaskFlow
</p>
</div>
</footer>
<a class="help-button" href="#contact">
문의
</a>
</body>
</html>
style.css 전체 파일
:root {
color: #171717;
background: #f5f5f5;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
Arial,
sans-serif;
line-height: 1.6;
color: #171717;
background: #f5f5f5;
}
.page {
width: min(
100% - 32px,
960px
);
margin-inline: auto;
}
.site-header,
.site-footer {
padding-block: 20px;
}
main {
padding-block: 48px;
}
section + section {
margin-top: 64px;
}
.card {
padding: 24px;
border: 1px solid #d7d7d7;
background: #fff;
}
form {
display: grid;
gap: 16px;
}
.form-field {
display: grid;
gap: 8px;
}
input,
textarea {
width: 100%;
min-height: 44px;
padding: 10px 12px;
border: 1px solid #b8b8b8;
background: #fff;
}
button {
min-height: 44px;
padding: 10px 16px;
border: 1px solid #171717;
background: #171717;
color: #fff;
}
a { color: inherit; }
input, textarea, button { font: inherit; }
h1 { font-size: 32px; font-weight: 700; }
h2 { font-size: 24px; }
h3 { font-size: 20px; }
.site-header__inner {
display: flex;
flex-direction: column;
gap: 12px;
}
.nav {
display: flex;
flex-wrap: wrap;
gap: 8px 16px;
}
.feature-list {
display: flex;
flex-direction: column;
gap: 16px;
}
@media (min-width: 48rem) {
.site-header__inner {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.feature-list {
flex-direction: row;
}
.feature-list > * {
flex: 1;
}
}
.site-header {
position: sticky;
top: 0;
z-index: 10;
background: #f5f5f5;
border: 1px solid #ddd;
}
.feature-card {
position: relative;
}
.feature-card__badge {
position: absolute;
top: 12px;
right: 12px;
padding: 4px 8px;
border: 1px solid #171717;
background: #fff;
font-size: 12px;
}
.help-button {
position: fixed;
right: 16px;
bottom: 16px;
min-width: 44px;
min-height: 44px;
padding: 10px 14px;
border: 1px solid #171717;
background: #171717;
color: #fff;
text-decoration: none;
}
.faq {
display: grid;
gap: 12px;
}
.faq details {
border: 1px solid #d7d7d7;
background: #fff;
}
.faq summary {
padding: 16px;
cursor: pointer;
font-weight: 700;
}
.faq details p {
margin: 0;
padding: 0 16px 16px;
}
.faq summary:focus-visible {
outline: 2px solid #171717;
outline-offset: 2px;
}
.feature-grid,
.pricing-grid {
display: grid;
grid-template-columns:
repeat(
auto-fit,
minmax(
min(100%, 220px),
1fr
)
);
gap: 16px;
}
.task-preview {
display: flex;
align-items: flex-start;
gap: 12px;
}
.task-preview__content {
min-width: 0;
flex: 1;
}
.task-preview__content p {
overflow-wrap: anywhere;
}
.settings-layout {
display: grid;
grid-template-columns:
180px
minmax(0, 1fr);
gap: 24px;
}
.table-scroll {
overflow-x: auto;
}
.pricing-table {
width: 100%;
min-width: 640px;
border-collapse: collapse;
background: #fff;
}
.pricing-table caption {
margin-bottom: 12px;
text-align: left;
font-weight: 700;
}
.pricing-table th,
.pricing-table td {
padding: 12px;
border: 1px solid #d7d7d7;
text-align: left;
}
위 파일과 ZIP의 동명 파일은 동일합니다. 회차별 README·CHANGES 문서는 ZIP에 포함합니다. 생성 산출물·node_modules·실제 환경 변수 파일은 포함하지 않습니다.
index.html
<section
id="pricing"
aria-labelledby="pricing-title"
>
<h2 id="pricing-title">
요금제
</h2>
<div class="table-scroll">
<table class="pricing-table">
<caption>
TaskFlow 요금제 기능 비교
</caption>
<thead>
<tr>
<th scope="col">
항목
</th>
<th scope="col">
Basic
</th>
<th scope="col">
Pro
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">
프로젝트
</th>
<td>3개</td>
<td>무제한</td>
</tr>
<tr>
<th scope="row">
작업 보관
</th>
<td>30일</td>
<td>무제한</td>
</tr>
<tr>
<th scope="row">
팀 공유
</th>
<td>없음</td>
<td>지원</td>
</tr>
</tbody>
</table>
</div>
</section>
style.css
.table-scroll {
overflow-x: auto;
}
.pricing-table {
width: 100%;
min-width: 640px;
border-collapse: collapse;
background: #fff;
}
.pricing-table caption {
margin-bottom: 12px;
text-align: left;
font-weight: 700;
}
.pricing-table th,
.pricing-table td {
padding: 12px;
border: 1px solid #d7d7d7;
text-align: left;
}
시작본과 이번 변경
CSS overflow 해결: 긴 텍스트·min-width: 0·말줄임 실습의 완료 체크포인트를 시작본으로 사용합니다.
index.html pricing section에 caption·scope가 있는 비교표를 반영하고 style.css에서 table wrapper에만 overflow-x:auto를 적용합니다. 360px에서 페이지 전체가 아닌 표만 움직이는지 확인합니다.
연속 프로젝트 선수 조건
- 기본 HTML 문서 구조와 overflow 개념을 알고 있어야 합니다.
- 표를 레이아웃 용도가 아니라 행·열 관계가 있는 데이터에 사용한다는 전제로 진행합니다.
이전 체크포인트
publishing-step-07-overflow
이번 목표
TaskFlow 요금제를 비교하는 접근 가능한 표를 추가합니다.
실행 확인
- 360px에서 페이지 자체는 가로 스크롤되지 않는가
- 표 wrapper 내부만 좌우로 움직이는가
- caption이 표 주제를 설명하는가
- 각 행/열 header가 th로 되어 있는가
완료 체크포인트
publishing-step-08-table
다음 학습
다음 글에서는 이미지가 안 보이거나 찌그러지고 레이아웃을 밀어내는 문제를 경로·비율·CLS 기준으로 해결합니다.
글 소개·학습 준비·전체 목차

웹 퍼블리싱 · 8 / 15 · 필수 학습 · 접근 가능한 표
표는 행과 열의 관계가 중요한 데이터를 표현할 때 사용하는 HTML 구조입니다.
레이아웃을 맞추기 위해 table을 쓰거나, 반대로 모바일 때문에 모든 표를 div 카드로 바꾸는 것 모두 좋은 기본값은 아닙니다.
이번 글에서는 caption, thead, tbody, th, scope를 이용해 요금제 비교표를 만들고 작은 화면에서는 표 영역만 가로 스크롤되도록 구성합니다.
이번 글에서 배우는 내용
- table의 역할
- caption
- thead와 tbody
- th와 td
- scope=”col” / “row”
- 모바일 table wrapper
- 전체 페이지 overflow와 국소 overflow 구분
먼저 알아야 할 내용
HTML 행·열 구조와 CSS overflow를 알면 됩니다. 표의 의미와 스크롤 wrapper를 각각 확인합니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 15개 · 필수 13개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.