의미가 있는 문서 구조
120분 안팎
학습 목표
제목 계층과 랜드마크로 행사 소개를 구성합니다.
개념
모양보다 문서의 역할을 먼저 정합니다
지역 행사 소개는 큰 글자 몇 줄의 모음이 아닙니다. 사용자는 이 사이트가 무엇을 제공하는지 알고 검색하거나 행사 내용을 읽으러 이동합니다. 내용을 header, nav, main, section, article, footer의 역할에 맞춰 나누면 화면 스타일이 없어도 순서와 책임을 이해하기 쉽습니다. 태그를 선택한 이유를 설명하는 일은 디자인 없이 문서를 읽어 보는 데서 시작합니다. 방문자의 첫 질문에 답하는 소개, 검색 진입점, 예시 목록을 순서대로 둡니다.
랜드마크와 제목은 서로 보완합니다
header에는 사이트 정체성과 시작 안내를 둡니다. nav에는 주요 이동 링크를 모으고 aria-label로 주요 메뉴라는 이름을 제공합니다. main은 이 페이지의 핵심 내용을 감쌉니다. 이 프로젝트는 숨겨진 main 없이 하나를 사용합니다. footer에는 교육용 정보라는 안내를 둡니다. 모든 묶음을 nav로 감싸면 메뉴의 의미가 흐려지고 main을 항목마다 넣으면 문서의 중심을 파악하기 어렵습니다. 태그를 많이 쓰는 것이 목표가 아니라 내용의 역할과 맞추는 것이 목표입니다.
h1은 이 문서가 무엇인지 알려 주는 우리 동네 행사 찾기입니다. h2는 소개, 검색, 목록 같은 주요 내용의 제목이며 h3는 목록 안의 개별 행사 제목입니다. 제목 숫자는 글자 크기 설정이 아니라 내용의 계층입니다. 글자를 작게 하고 싶다는 이유로 h1 아래 h4를 쓰지 않습니다. 글자 크기와 간격은 다음 모듈의 CSS에서 조정합니다. 이 단계에서는 h1 하나와 h2, h3 순서를 프로젝트 계약으로 정해 검사하기 쉽게 만듭니다.
section과 article의 선택
section은 주제가 있는 내용 묶음이며 소개처럼 제목으로 설명할 수 있을 때 사용합니다. aria-labelledby에 intro-title을 적으면 해당 id를 가진 제목을 참조합니다. id를 바꾸면 참조 값도 같이 바꾸고 하나의 문서에서 id가 중복되지 않게 합니다. 제목이 없는 단순 여백용 묶음까지 section으로 만들 필요는 없습니다. 레이아웃을 위한 일반 묶음은 div로 표현할 수 있습니다. 의미 있는 요소가 있어도 추가 속성만으로 모든 접근성 문제가 해결되지는 않습니다.
article은 따로 읽어도 의미가 있는 행사 소개 한 건에 사용합니다. 행사 이름, 지역, 일정, 상세 링크를 하나로 묶으면 목록 밖에서도 내용을 이해할 수 있습니다. 모든 문단을 article로 감싸는 것은 역할을 과장합니다. 목록에 여러 행사가 생기면 각각의 제목을 같은 h3 수준에 둡니다. 순서 있는 절차와 순서 없는 목록도 구별합니다. 지금 예시 목록에는 한 건만 있지만 다음 단계에서 반복 목록을 만들 때 같은 구조를 확장할 수 있습니다.
링크는 어디로 갈지 알려 줍니다
다른 위치나 문서로 이동하는 동작은 a와 href로 만듭니다. 여기 클릭이라는 문구만 있으면 주변 내용을 읽어야 목적지를 알 수 있습니다. 동네 책 나눔 상세 보기라는 문구는 링크만 읽어도 대상과 행동을 설명합니다. 소개 링크는 #intro, 목록 링크는 #events로 연결하며 해당 목적지는 section의 id로 지정합니다. 링크의 이름이 좋아도 목적지가 없으면 사용자 흐름이 끊기므로 문구와 경로를 같이 검증합니다.
버튼처럼 보이는 div에 클릭 처리를 붙이는 방법은 기본 키보드 동작을 다시 구현해야 합니다. 이동 링크를 버튼으로 바꾸거나 제출 버튼을 링크로 바꾸는 것도 동작의 의미를 흐립니다. 지금은 자바스크립트 없이도 이동이 되는 문서를 만들며 나중에 상호작용을 추가해도 이 구조를 출발점으로 유지합니다. 검색 버튼은 폼 제출이라는 작업이므로 다음 레슨에서 button으로 다룹니다.
구조를 리뷰하는 방법
스타일을 잠시 무시하고 제목만 차례로 읽습니다. 사이트 이름이 반복되거나 소개보다 상세 항목이 먼저 나오면 사용자 순서와 문서 순서가 맞는지 살핍니다. 검사에서 main과 h1 각각 하나가 실패하면 전체 파일에서 여는 태그 수를 확인합니다. 닫는 태그 누락으로 DOM이 예상과 달라질 수도 있으므로 개발자 도구의 실제 부모 관계도 확인합니다. 자동 검사에 없는 내용 품질은 동료에게 제목과 링크 문구만 읽어 달라고 요청해 점검합니다.
태그 이름과 실제 의미를 대조합니다
본문에 등장하는 header가 모두 페이지 전체의 머리말인 것은 아닙니다. article 안의 header는 그 글의 머리말로 사용할 수 있습니다. footer 역시 글 내부의 작성 정보에 쓸 수 있으므로 요소의 위치와 조상을 함께 봅니다. 현재 예제에서는 사이트 머리말과 꼬리말을 main 밖에 배치합니다. 이 배치는 주요 내용과 반복 안내를 구분하려는 선택입니다. 요소 이름만 찾는 검사와 실제 문서에서 랜드마크로 노출되는지 확인하는 작업에는 차이가 있습니다. 개발자 도구의 접근성 정보를 열어 각 영역에 어떤 역할과 이름이 제공되는지 비교합니다.
section에 aria-labelledby를 연결하면 제목 문구로 영역 이름을 제공할 수 있습니다. 소개 제목의 id가 intro-title인데 참조를 intro라고 쓰면 section 자신의 id와 제목 id를 혼동한 것입니다. 영역의 이름을 바꾸고 싶을 때는 제목 문구를 먼저 검토합니다. 눈에 보이는 제목과 별도의 aria-label을 서로 다르게 작성하면 읽는 방식에 따라 다른 정보를 접할 수 있습니다. 이 예제처럼 실제 제목을 참조하면 제목 수정과 영역 이름이 함께 바뀌어 관리하기 쉽습니다.
행사가 늘어날 때의 구조
두 번째 행사로 동네 그림 전시를 추가한다고 가정합니다. 기존 article 안에 새 행사 제목을 끼워 넣지 않고 형제 article을 만듭니다. 두 행사는 모두 예시 행사 목록이라는 h2 아래에 있으므로 행사 제목은 각각 h3입니다. 첫 행사의 상세 링크와 두 번째 행사의 상세 링크가 모두 상세 보기라고만 쓰여 있다면 링크 목록을 따로 읽을 때 대상을 구별하기 어렵습니다. 각 링크 문구에 행사 이름을 포함합니다. 행사마다 소개와 일정이라는 하위 구획이 필요해지면 그 안의 제목 수준을 추가로 설계합니다.
문서 구조의 완료 근거
소개에서 행사 목록까지 제목을 순서대로 적고 각 제목 아래 어떤 내용이 속하는지 설명합니다. main 하나라는 수량 조건만 만족해도 중요한 소개 문단이 main 밖에 있으면 중심 내용 구성이 어색할 수 있습니다. article이 있다는 사실만으로 행사 이름과 일정이 그 안에 들어 있다는 보장도 없습니다. 요소 탭에서 부모와 자식 관계를 펼쳐 실제 내용의 범위를 확인합니다. 검토 메모에는 소개는 section, 독립 행사 한 건은 article, 주요 이동은 nav로 선택한 이유를 적습니다. 이 설명이 내용과 맞으면 이후 CSS로 위치를 바꾸어도 문서 역할을 유지할 기준이 생깁니다.
따라하기
실습 폴더 준비
starter 압축을 풀고 그 폴더를 편집기로 엽니다. 터미널도 package.json이 있는 폴더에서 시작합니다. 다음 명령은 외부 의존 패키지가 없는 잠금 파일을 사용합니다.
npm ci
npm teststarter의 FAIL 항목을 먼저 읽습니다. npm 설치나 Python 실행 실패는 환경 문제이므로 HTML 수정 전에 해결합니다.
계약에 맞게 문서 수정
index.html에서 다음 구조를 기준으로 해당 레슨의 FAIL 항목을 고칩니다. 아래는 최종 문서의 참고 코드이며 그대로 저장하면 같은 파일 구성에서 사용할 수 있습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>우리 동네 행사 찾기</title>
<link rel="stylesheet" href="assets/site.css">
</head>
<body>
<header><p>우리 동네 행사</p><nav aria-label="주요 메뉴"><a href="#intro">소개</a> <a href="#events">행사 목록</a></nav></header>
<main>
<h1>우리 동네 행사 찾기</h1>
<section id="intro" aria-labelledby="intro-title">
<h2 id="intro-title">가까운 곳에서 함께 만나요</h2>
<p>지역과 검색어로 관심 있는 행사를 찾아보세요. 현재는 예시 행사만 제공합니다.</p>
</section>
<section aria-labelledby="search-title">
<h2 id="search-title">행사 검색</h2>
<p id="query-help">검색어를 한 글자 이상 입력하세요. 예: 책</p>
<form action="results.html" method="get">
<label for="query">검색어</label>
<input id="query" name="q" type="search" required aria-describedby="query-help">
<label for="region">지역</label>
<select id="region" name="region">
<option value="all">전체 지역</option>
<option value="seoul">서울</option>
<option value="busan">부산</option>
</select>
<button type="submit">검색</button>
</form>
</section>
<section id="events" aria-labelledby="events-title">
<h2 id="events-title">예시 행사 목록</h2>
<article><h3>동네 책 나눔</h3><p>서울 · 10월 20일 · 무료</p><a href="event.html">동네 책 나눔 상세 보기</a></article>
</section>
</main>
<footer><p>교육용 행사 정보이며 실제 모집 공고가 아닙니다.</p></footer>
</body>
</html>
파일 검사 재실행
저장한 뒤 실습 폴더에서 검사합니다. 마지막 실패 수가 0인지 확인합니다. 출력은 solution을 실행한 실제 검사 결과입니다.
npm test실행 결과
> frontend-m01-semantic-page@1.0.0 test > python3 check.py PASS 한국어 문서와 제목 PASS UTF-8 및 작은 화면 설정 PASS 스타일 파일 상대 경로 PASS 목록 및 상세 파일 존재 PASS 중복 id 없음 PASS 모든 내부 링크 목적지 존재 PASS main과 h1 각각 하나 PASS 주요 메뉴와 행사 article PASS 소개와 목록 제목 연결 PASS 행사 제목 h3 검사 10개, 실패 0개
브라우저에서 사용자 흐름 확인
index.html을 브라우저의 파일 열기로 엽니다. 소개와 목록 링크, 상세 페이지와 돌아가기를 확인합니다. 검색어에 책을 넣고 지역을 서울로 골라 Enter를 누릅니다. results.html 주소의 q와 region을 확인합니다. 다시 돌아와 빈 입력 제출과 Tab·Shift+Tab을 시험하고 KEYBOARD.md의 미확인을 실제 관찰로 바꿉니다. 브라우저 결과는 직접 관찰하므로 고정 출력이 없습니다.
확인 문제
실습
starter의 index.html을 수정해 이 레슨의 검사 계약을 통과시키세요. package.json 폴더에서 npm ci 후 npm test를 실행합니다. check.py는 수정하지 않습니다. 브라우저에서 링크·검색·키보드 흐름을 확인하고 KEYBOARD.md에 관찰 결과를 기록합니다. solution은 비교용이며 수동 검증 결과를 대신하지 않습니다.
실행 명령
npm test
기대 결과
모든 PASS 항목과 마지막 실패 0개를 확인합니다. 수동 점검 기록도 제출합니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- section과 article은 어떤 기준으로 구분하나요?
- 제목 수준을 글자 크기로 선택하면 어떤 문제가 생기나요?