02. HTML과 문서 구조
무엇을 쓰느냐가 접근성과 검색, 기본 동작을 결정합니다. 요소를 고르는 기준을 정리합니다.
제목, 문단, 목록, 링크와 이미지
제목은 크기가 아니라 구조입니다. h1부터 단계를 건너뛰지 않고 내려갑니다.
<h1>학습 로드맵</h1>
<h2>프론트엔드</h2>
<h3>HTML</h3>크기를 줄이려고 h3을 쓰는 것이 아니라, CSS로 크기를 조절합니다. 스크린 리더 사용자는 제목만 훑어 문서 구조를 파악하기 때문입니다.
목록은 순서가 의미 있으면 ol, 아니면 ul입니다. 용어와 설명은 dl입니다.
<dl>
<dt>DOM</dt>
<dd>문서를 객체로 표현한 트리</dd>
</dl>이미지에는 대체 텍스트를 적습니다. 장식용이면 빈 값으로 둡니다. 빼면 스크린 리더가 파일명을 읽습니다.
<img src="chart.png" alt="월별 완료 건수 추이" />
<img src="divider.png" alt="" />의미에 맞는 요소 선택
<body>
<header>로고, 전역 검색</header>
<nav>주요 메뉴</nav>
<main>
<h1>할 일</h1>
<section>
<h2>오늘</h2>
</section>
<article>독립적으로 떼어 내도 말이 되는 덩어리</article>
</main>
<aside>본문과 간접적으로 관련된 것</aside>
<footer>저작권, 연락처</footer>
</body>main은 문서에 하나입니다. 보조 기술이 "본문으로 건너뛰기"의 목표로 삼습니다.
section은 제목을 가져야 의미가 있습니다. 제목 없이 묶기만 할 거라면 div가 맞습니다.
<!-- 의미 없는 section -->
<section><p>텍스트</p></section>
<!-- div로 충분합니다 -->
<div class="wrapper"><p>텍스트</p></div>같은 역할의 영역이 여러 개면 이름을 붙여 구분합니다.
<nav aria-label="주요 메뉴">...</nav>
<nav aria-label="페이지 내 목차">...</nav>button과 a의 역할 구분
기준은 이동이냐 동작이냐입니다.
<a href="/learn/frontend/">페이지로 이동</a>
<button type="button">항목 삭제</button>div에 클릭 이벤트를 붙이면 겉보기에는 같지만 여러 가지를 잃습니다.
<!-- 이렇게 하면 -->
<div onclick="remove()">삭제</div>- 탭으로 도달할 수 없습니다
- Enter와 Space로 실행되지 않습니다
- 스크린 리더가 버튼이라고 알리지 않습니다
굳이 쓴다면 tabindex="0", role="button", 키보드 핸들러를 전부 직접 붙여야 합니다. button을 쓰면 공짜로 따라옵니다.
폼 안의 button은 기본 타입이 submit입니다. 제출이 아니라면 명시합니다.
<form>
<button>제출됩니다</button>
<button type="button">제출되지 않습니다</button>
</form>form, label, input과 기본 입력 검증
모든 입력에는 레이블이 붙어야 합니다.
<label for="title">할 일</label>
<input id="title" name="title" />
<!-- 감싸는 방식도 됩니다 -->
<label>
할 일
<input name="title" />
</label>placeholder는 레이블이 아닙니다. 입력을 시작하면 사라져서 무엇을 적는 칸인지 알 수 없게 됩니다.
브라우저가 제공하는 검증을 먼저 씁니다.
<input name="title" required maxlength="100" />
<input type="email" name="email" />
<input type="number" name="count" min="1" max="99" />
<input name="code" pattern="[A-Z]{3}-\d{4}" />form으로 감싸면 Enter로 제출되고, 브라우저가 검증 메시지를 띄웁니다. 자바스크립트 없이도 동작하는 기반이 생깁니다.
form.addEventListener("submit", (event) => {
event.preventDefault(); // 기본 제출을 막고 직접 처리
const data = new FormData(form);
data.get("title");
});name이 없는 입력은 FormData에 담기지 않습니다.
input type과 모바일 키보드, 자동 완성
타입에 따라 모바일에서 뜨는 키보드가 달라집니다.
<input type="email" /> <!-- @ 키가 있는 키보드 -->
<input type="tel" /> <!-- 숫자 패드 -->
<input type="url" />
<input type="search" />
<input type="date" />숫자를 받을 때 type="number"는 스피너가 붙고 복사·붙여넣기에서 문제가 생기기도 합니다. 전화번호나 카드번호처럼 계산하지 않는 숫자에는 inputmode가 낫습니다.
<input type="text" inputmode="numeric" pattern="\d*" />자동 완성은 autocomplete로 안내합니다. 사용자의 입력 수고를 크게 줄입니다.
<input name="email" autocomplete="email" />
<input name="name" autocomplete="name" />
<input type="password" autocomplete="current-password" />
<input type="password" autocomplete="new-password" />table의 구조
표에는 정해진 뼈대가 있습니다.
<table>
<caption>월별 완료 건수</caption>
<thead>
<tr>
<th scope="col">월</th>
<th scope="col">건수</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1월</th>
<td>12</td>
</tr>
</tbody>
</table>scope는 그 머리 칸이 열을 설명하는지 행을 설명하는지 알려 줍니다. 이것이 있어야 스크린 리더가 "1월, 건수 12"처럼 읽어 줍니다.
표는 데이터를 위한 것입니다. 레이아웃에는 Grid나 Flexbox를 씁니다.
dialog, details 등 동작이 내장된 요소
예전에는 직접 만들던 것들이 표준 요소로 들어와 있습니다.
<dialog id="confirm">
<form method="dialog">
<p>삭제할까요?</p>
<button value="cancel">취소</button>
<button value="ok">삭제</button>
</form>
</dialog>confirm.showModal(); // 배경 클릭 차단, 포커스 가두기, Esc 닫기가 기본 동작
confirm.returnValue; // "ok" 또는 "cancel"모달에서 가장 구현하기 까다로운 포커스 관리가 내장되어 있습니다.
<details>
<summary>자세히</summary>
<p>펼쳐지는 내용</p>
</details>JavaScript 없이 접고 펼칩니다. 같은 name을 주면 아코디언처럼 하나만 열립니다.
<details name="faq"><summary>질문 1</summary>답</details>
<details name="faq"><summary>질문 2</summary>답</details>문서 메타데이터
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>할 일 관리</title>
<meta name="description" content="검색 결과에 표시되는 설명" />
<link rel="canonical" href="https://example.com/todos" />
<meta property="og:title" content="할 일 관리" />
<meta property="og:description" content="공유 시 미리보기 설명" />
<meta property="og:image" content="https://example.com/og.png" />
</head>viewport가 없으면 모바일에서 데스크톱 너비로 렌더링한 뒤 축소해 보여 줍니다. 반응형 CSS가 통째로 무시됩니다.
title은 탭과 검색 결과, 북마크에 쓰입니다. 페이지마다 다르게 적습니다.