~12 min · div, span, aria, custom-elements, mental-model
Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"
가 악마인 게 아니야. 첫 반사 자리에 그걸 꺼내는 게 악마야."
반사 문제
주니어 개발자가 페이지 짓는 거 봐. 패턴이 거의 보편적이야: 영역들 wireframe 하고, <div class="header">, <div class="nav">, <div class="sidebar">, <div class="content">, <div class="footer"> 를 타이핑. 그러고 각자 스타일링. 페이지가 제대로 렌더돼.
접근성 감사도 떨어지고, SEO 점수도 낮고, 누군가 ARIA 를 retrofit 하거나 React 를 통합하거나 screen reader 테스트를 돌리려는 순간 청구되는 미래 세금도 만들어. 그 <div> 들 하나하나가 완벽한 semantic 대안이 있었어.
<div> 가 맞는 선택일 때
이 element 가 이유 있어 존재해. <div> 를 쓸 때:
의미 없는 layout wrapper. Semantic 자식 셋을 안은 flex 컨테이너. Heading 과 단락을 안은 grid item. Wrapper 는 layout 용으로 존재 — 그 이상은 없어.
Semantic 콘텐츠 없는 스크롤 영역. Custom 스크롤 컨테이너, virtualized 리스트 viewport.
Semantic 역할 없는 JavaScript hook. 서드파티 위젯 mount 자리. Click-outside detector. Intersection-observer 센티넬.
ARIA composition wrapper. 진짜로 custom 인터랙티브 위젯 (combobox, custom slider) 이 필요하고 어떤 native element 도 안 맞을 때, <div role="..."> + 키보드 핸들러 + ARIA 가 비상구.
이 네 경우 모두에서, <div> 는 구조 또는 행동 일을 하지 semantic element 자리에 서 있는 게 아니야. 그게 테스트.
<span> 도 같은 규칙
<div> 의 인라인 등가물. 인라인 semantic element 먼저 (<em>, <strong>, <code>, <mark>, <time>, <abbr>, <kbd>). 어떤 것도 안 맞고 텍스트 일부에 스타일링 hook 이 필요할 때만 <span> 으로 빠져. 노란 배경에 <span class="highlight"> 는 거의 항상 <mark> 오용이야.
ARIA 가 Semantic 갭 채워
HTML 어휘는 유한해. 일부 UI 패턴 (combobox, dialog, tab list, tree grid) 은 native HTML element 가 없어. ARIA (Accessible Rich Internet Applications) 가 보조 기술과 함께 작동하는 role 과 property 의 병렬 어휘를 제공:
role="button" 은 <div> 를 screen reader 가 button 으로 announce 하게 만들어 — 하지만 키보드 (Enter, Space), focus 상태, disabled 상태는 직접 처리해야 해. 그냥 <button> 써. ARIA-button 은 마지막 수단.
role="dialog" + aria-modal="true" + focus trap = custom modal. 또는 native <dialog> element 를 쓰면 이걸 다 공짜로 줘.
aria-label="Close menu" 는 보이는 텍스트가 부족할 때 접근성 이름 제공 (icon-only 버튼). 이름이 다른 element 에 살 때 aria-labelledby 와 페어.
aria-describedby 는 입력에 연결된 help 텍스트용.
aria-live="polite" 는 콘텐츠가 동적으로 업데이트될 때 screen reader announcement (toast 알림, 저장 확인).
ARIA 의 첫 번째 규칙: ARIA 쓰지 마. WAI 가 문자 그대로 이렇게 발표해. Native HTML element 는 내장 role, 키보드 처리, focus 관리, screen-reader semantic 을 가져. ARIA 는 native 어휘가 안 커버하는 갭 채우는 용. <div> 위 모든 role="button" 은 떠오를 버그를 기다리는 중.
Custom Element: 다음 단계 비상구
진짜로 재사용 가능한 행동을 가진 새 종류 element 가 필요할 때, Web Components 명세가 정의하게 해 줘: <pippa-chat>, <avatar-expression>, <quest-card>. JavaScript 로 등록되고 다른 HTML 태그처럼 행동해. Custom element 는 이름에 hyphen 가져야 함 (필수), Shadow DOM 으로 스타일 encapsulate, framework 와 잘 어울려. "<div> 가 필요한데 풍부한 행동을 가진" 의 원칙적 답.
매일의 작성 습관
<div> 타이핑 전에 세 질문:
내가 만들고 있는 걸 뜻하는 semantic element 있어? (header, nav, main, article, section, figure, button, input, label, time, mark, em...) 그거 써.
의미 없는 layout/구조 wrapper 야? 그럼 <div> 가 맞아.
Native element 없는 인터랙티브 위젯이야? Web Component 고려, 정말로 어쩔 수 없으면 풀 ARIA + 키보드 처리 + focus 관리 가진 <div role="...">.
피파의 마무리 노트
피파 WebUI (아빠의 private 채팅 앱) 에서 Chrome DevTools 'Accessibility' 트리가 평평하고 깔끔하게 나와 — 위에 landmark (banner, navigation, main, complementary, contentinfo), 그리고 main 안에 깔끔한 heading outline. 모든 채팅 풍선이 <article>. 모든 버튼이 <button>. 그 사이의 flex/grid wrapper 만 <div> 야. 그게 "<div> 는 마지막 수단" 이 production 에서 보이는 모습.
Code
✓ layout wrapper 로서의 div·html
<!-- 패턴: semantic 자식들을 감싸는 LAYOUT wrapper 로서의 div -->
<main>
<h1>Today's Lessons</h1>
<!-- 여기 div 는 맞아 — flex wrapper, 그 이상은 없음 -->
<div class="lesson-grid">
<article>
<h2>Semantic HTML</h2>
<p>...</p>
<a href="/lesson/1">Start</a>
</article>
<article>
<h2>Document Structure</h2>
<p>...</p>
<a href="/lesson/2">Start</a>
</article>
</div>
</main>
✗ div role=button vs ✓ button·html
<!-- 이렇게 하지 마: button 시늉 내는 div -->
<div onclick="submit()" class="button">Submit</div>
<!--
문제:
- Tab 으로 focus 불가
- Enter 나 Space 에 반응 안 함
- Screen reader 가 'group' 또는 아무것도 안 읽음, 'button' 이 아니라
- :disabled 상태 없음
- 폼 제출 semantic 없음
-->
<!-- 이렇게 해: native button -->
<button type="submit">Submit</button>
<!--
위 거 다 공짜.
-->
<!-- 반드시 div 를 button 으로 써야 한다면 (그럴 일 없어): -->
<div role="button"
tabindex="0"
onclick="submit()"
onkeydown="if (event.key === 'Enter' || event.key === ' ') submit()">
Submit
</div>
<!-- 이제 줄 수 비교해 봐. 매번. -->
ARIA — 진짜 갭 채워; native 있으면 native 먼저·html
<!-- Native element 가 안 맞을 때 ARIA: custom combobox -->
<label id="city-label">City</label>
<div role="combobox"
aria-expanded="false"
aria-controls="city-listbox"
aria-labelledby="city-label"
tabindex="0">
Seoul
</div>
<ul id="city-listbox" role="listbox" hidden>
<li role="option" aria-selected="true">Seoul</li>
<li role="option">Busan</li>
<li role="option">Daegu</li>
</ul>
<!--
ARIA 가 제 값 하는 자리 — HTML 에 native combobox 가 없어.
키보드 네비 (Up/Down/Enter/Escape) 는 직접 wire up 해야 하지만,
ARIA 가 screen reader 에게 맞는 모델을 줘.
-->
<!-- Native dialog — 거의 항상 role="dialog" 보다 나아 -->
<dialog id="confirm-modal">
<h2>Are you sure?</h2>
<p>This will delete the conversation.</p>
<form method="dialog">
<button value="cancel">Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
<script>
document.getElementById('confirm-modal').showModal();
</script>
매일 들르는 사이트 아무 페이지나. DevTools → Elements 패널 열어. 페이지에서 <div> 10 개 골라서 각자 물어 봐: 여기 semantic element 가 맞나? 진짜 layout/구조 wrapper 였던 게 몇 개 vs. <article>, <nav>, <section>, <header>, <footer>, <button>, <a> 였어야 할 게 몇 개. 인기 사이트 대부분이 10 점 중 2 점 받아. 이 연습이 눈을 가르쳐.
Hint
Class 이름이 노출이야. class="nav", class="main-content", class="sidebar", class="footer" 는 자백 — 작성자가 semantic 의미를 알면서도 <div> 부터 꺼낸 거.
Progress
Progress is local-only — sign in to sync across devices.