콘텐츠 모델 이해가 왜 중요할까?

콘텐츠 모델을 정확히 이해하는 것이 유효한 마크업 작성, 접근성 향상, 예측 가능한 브라우저 렌더링에 어떻게 기여하는지 종합적으로 학습합니다

중급 15분 유효한 마크업 접근성 렌더링 모범 사례

HTML을 작성할 때 우리는 흔히 태그의 이름과 속성에만 집중하지만, 각 요소가 어떤 종류의 콘텐츠를 포함할 수 있는지를 정의하는 콘텐츠 모델이야말로 올바른 마크업의 핵심 기반입니다. 콘텐츠 모델은 HTML 명세에서 모든 요소에 대해 “이 요소 안에 무엇이 들어갈 수 있는가”와 “이 요소는 어디에 위치할 수 있는가”를 규정하는 체계적인 분류 시스템입니다. 이 규칙을 이해하지 못하면, 눈에는 정상적으로 보이지만 실제로는 명세 위반인 마크업을 작성하게 되고, 이는 브라우저 간 일관성 없는 렌더링과 접근성 문제로 이어집니다. 콘텐츠 모델을 정확히 파악하는 것은 단순한 문법 지식이 아니라, 웹 표준을 준수하는 견고한 문서 구조를 설계하는 능력의 출발점입니다.

🔍 핵심 특징

  • 요소 간 포함 관계의 명확한 규칙: 콘텐츠 모델은 각 HTML 요소가 자식으로 허용하는 콘텐츠 카테고리(flow, phrasing, embedded 등)를 명확히 정의하여, 어떤 요소를 어디에 중첩할 수 있는지 판단하는 기준을 제공합니다
  • 유효성 검증의 근거: W3C Validator나 브라우저의 파싱 알고리즘이 마크업의 유효성을 판단할 때, 콘텐츠 모델 규칙이 핵심 기준으로 작동합니다
  • 브라우저 오류 복구 동작의 이해: 콘텐츠 모델을 위반한 마크업에 대해 브라우저가 수행하는 자동 교정(요소 재배치, 암묵적 닫기 등)의 원리를 이해할 수 있습니다
  • 접근성과 시맨틱의 기초: 스크린 리더와 보조 기술은 콘텐츠 모델에 기반한 올바른 요소 중첩을 전제로 동작하며, 위반 시 콘텐츠 해석에 심각한 오류가 발생할 수 있습니다
  • 프레임워크 시대에도 유효한 기본기: React, Vue 등 컴포넌트 기반 개발에서도 최종 렌더링 결과는 HTML이므로, 콘텐츠 모델 이해 없이는 컴포넌트 조합 시 잘못된 DOM 구조를 생성할 수 있습니다

💡 실무에서의 영향

콘텐츠 모델에 대한 정확한 이해는 실무에서 여러 측면의 품질 향상으로 직결됩니다. 첫째, 유효한 마크업은 브라우저 간 렌더링 차이를 최소화하여, “내 브라우저에서는 잘 보이는데 다른 브라우저에서는 깨진다”는 문제를 원천적으로 줄여줍니다. 둘째, 올바른 요소 중첩은 접근성 트리를 정확하게 구성하므로, WCAG 준수와 스크린 리더 호환성을 자연스럽게 확보할 수 있습니다. 셋째, SEO 관점에서도 검색 엔진 크롤러는 시맨틱하게 올바른 문서 구조를 더 정확히 해석하며, 이는 검색 노출과 구조화된 데이터 추출에 직접적으로 영향을 미칩니다. 넷째, 컴포넌트 라이브러리를 설계하거나 디자인 시스템을 구축할 때, 콘텐츠 모델을 기준으로 컴포넌트의 자식 요소 제약을 정의하면 잘못된 사용을 컴파일 타임이나 린팅 단계에서 사전에 방지할 수 있습니다. 결국 콘텐츠 모델 이해는 “동작하는 HTML”에서 “올바른 HTML”로 나아가는 전환점이며, 시니어 개발자로 성장하는 데 필수적인 웹 기초 역량입니다.


핵심 개념

콘텐츠 카테고리 분류 체계

입문

HTML의 모든 요소는 종류별로 그룹이 나뉘어 있어요. 이 그룹을 ‘콘텐츠 카테고리’라고 부르는데, 어떤 요소가 어디에 들어갈 수 있는지를 정하는 기준이 돼요.

📚 도서관의 분류 시스템처럼 도서관에 가면 책이 아무렇게나 꽂혀 있지 않죠? 소설은 소설 코너, 과학책은 과학 코너에 있어요. HTML 요소도 마찬가지로 ‘플로우’, ‘프레이징’, ‘임베디드’ 같은 그룹으로 나뉘어 있어요. 각 그룹에는 비슷한 역할을 하는 요소들이 모여 있답니다.

🏠 방마다 들어갈 수 있는 가구가 달라요 거실에는 소파와 TV가 어울리고, 욕실에는 세면대와 샤워기가 어울리죠. HTML에서도 특정 요소 안에는 특정 종류의 요소만 들어갈 수 있어요. 예를 들어, 문단(p) 안에는 글자 수준의 요소만 넣을 수 있고, 또 다른 문단이나 표는 넣을 수 없어요.

🎯 왜 이렇게 나눠놓았을까요? 만약 도서관에 분류 없이 책을 쌓아두면, 원하는 책을 찾기가 너무 어렵겠죠? HTML도 규칙 없이 아무 요소나 아무 곳에 넣으면 컴퓨터(브라우저)가 혼란스러워해요. 분류 체계 덕분에 브라우저는 각 요소의 역할을 정확히 이해하고 올바르게 화면에 보여줄 수 있어요.

💡 한 요소가 여러 그룹에 속할 수도 있어요 도서관에서 ‘역사 소설’이 소설 코너에도, 역사 코너에도 꽂힐 수 있는 것처럼, HTML 요소도 여러 카테고리에 동시에 속할 수 있어요. 예를 들어 이미지(img)는 ‘플로우’ 그룹이면서 동시에 ‘프레이징’ 그룹이고, ‘임베디드’ 그룹이기도 해요.

중급

HTML Living Standard는 모든 요소를 7가지 주요 콘텐츠 카테고리로 분류합니다. 이 분류는 각 요소의 콘텐츠 모델(허용되는 자식 요소)을 정의하는 데 사용됩니다.

7가지 콘텐츠 카테고리

  • Flow content: 문서 본문에 사용되는 대부분의 요소 (div, p, h1~h6, ul, table 등)
  • Phrasing content: 텍스트 수준의 인라인 요소 (span, a, em, strong, img 등)
  • Heading content: 섹션의 제목을 정의하는 요소 (h1~h6, hgroup)
  • Sectioning content: 문서의 구조적 영역을 정의하는 요소 (article, aside, nav, section)
  • Embedded content: 외부 리소스를 삽입하는 요소 (img, video, audio, iframe, canvas)
  • Interactive content: 사용자 상호작용이 가능한 요소 (a, button, input, select, textarea)
  • Metadata content: 문서 메타정보를 정의하는 요소 (meta, link, style, script, title)

하나의 요소는 여러 카테고리에 동시에 속할 수 있으며, 이 카테고리 소속이 곧 해당 요소가 어디에 위치할 수 있는지를 결정합니다.

<!-- p 요소의 콘텐츠 모델: phrasing content -->
<p>
  이 문단에는 <strong>phrasing content</strong>만 허용됩니다.
  <em>em</em>, <a href="#">a</a>, <span>span</span> 모두 가능합니다.
</p>

<!-- div 요소의 콘텐츠 모델: flow content -->
<div>
  이 안에는 <p>문단도</p>
  <ul><li>목록도</li></ul>
  <table><tr><td>표도</td></tr></table>
  거의 모든 flow content가 들어갈 수 있습니다.
</div>

심화

HTML Living Standard의 콘텐츠 카테고리 체계는 과거 HTML4의 단순한 블록/인라인 이분법을 대체하는 다차원 분류 모델(multi-dimensional classification model)로, 요소의 의미적 역할과 허용 문맥을 동시에 정의합니다.

WHATWG 명세의 콘텐츠 카테고리 설계 원칙 HTML Living Standard Section 3.2.5.2 “Kinds of content”에서 정의된 카테고리 체계는 집합론(Set Theory)의 교집합 관계를 활용합니다. 각 요소는 하나 이상의 카테고리 집합에 소속되며, 요소의 콘텐츠 모델은 이 카테고리 집합의 조합으로 표현됩니다.

예를 들어, <a> 요소는 flow content, phrasing content, interactive content에 동시에 속합니다. 그러나 <a>의 콘텐츠 모델은 “transparent”로 정의되어, 부모 요소의 콘텐츠 모델을 그대로 상속합니다. 이때 핵심 제약은 “interactive content를 자손으로 포함할 수 없다”는 규칙입니다. 이는 중첩 인터랙티브 요소(nested interactive elements)가 사용자 에이전트의 이벤트 디스패치(Event Dispatch) 메커니즘에서 모호성을 야기하기 때문입니다.

카테고리 간 포함 관계의 수학적 구조 카테고리 간에는 엄격한 부분집합(proper subset) 관계가 존재합니다. Phrasing content는 flow content의 부분집합이므로, flow content를 허용하는 위치에는 항상 phrasing content가 올 수 있지만 그 역은 성립하지 않습니다. 이 비대칭성(asymmetry)이 “p 안에 div를 넣을 수 없지만, div 안에 p를 넣을 수 있다”는 직관적 규칙의 이론적 근거입니다.

Sectioning content와 heading content의 관계는 문서 아웃라인 알고리즘(Document Outline Algorithm)과 직결됩니다. 각 sectioning content 요소는 새로운 아웃라인 스코프(outline scope)를 생성하며, heading content는 해당 스코프 내에서 암묵적 섹션(implied section)을 형성합니다. 이 메커니즘은 보조 기술(Assistive Technology)이 문서 구조를 탐색하는 기반이 됩니다.

중첩 규칙과 유효성 검증

입문

HTML에서는 요소를 다른 요소 안에 넣을 때 지켜야 하는 규칙이 있어요. 이 규칙을 어기면 웹페이지가 의도대로 보이지 않을 수 있어요.

📦 상자 안에 상자 넣기 규칙 큰 상자 안에 작은 상자를 넣을 수 있지만, 작은 상자 안에 큰 상자를 억지로 넣으면 뚜껑이 닫히지 않죠? HTML도 비슷해요. 큰 역할을 하는 요소(예: div) 안에는 거의 뭐든 넣을 수 있지만, 작은 역할을 하는 요소(예: 문단 p) 안에는 정해진 것만 넣을 수 있어요.

🚨 잘못 넣으면 어떻게 될까요? 친구에게 편지를 쓰는데 봉투 안에 택배 상자를 넣으려 하면 어떻게 될까요? 봉투가 찢어지거나 모양이 이상해지겠죠. HTML에서도 규칙에 맞지 않는 요소를 넣으면, 브라우저가 알아서 고치려고 하는데 그 결과가 우리 의도와 다를 수 있어요.

🔍 검사하는 방법이 있어요 맞춤법 검사기가 글의 틀린 부분을 알려주듯이, HTML에도 검사 도구(Validator)가 있어요. 이 도구에 우리가 작성한 HTML을 넣으면 “이 요소는 여기에 들어갈 수 없어요”라고 정확히 알려줘요.

💡 규칙을 왜 알아야 할까요? 요리할 때 레시피를 따르면 맛있는 요리가 나오듯이, HTML 규칙을 따르면 어떤 브라우저에서든 똑같이 예쁘게 보이는 웹페이지를 만들 수 있어요. 규칙을 모르고 아무렇게나 작성하면 어떤 브라우저에서는 잘 보이고, 어떤 브라우저에서는 깨져 보일 수 있답니다.

중급

콘텐츠 모델은 각 요소가 허용하는 자식 요소의 종류를 정의합니다. 이 규칙을 위반하면 마크업이 “무효(invalid)“하다고 판정되며, 브라우저마다 다른 방식으로 처리할 수 있습니다.

대표적인 중첩 위반 사례

  • <p> 안에 <div>: p의 콘텐츠 모델은 phrasing content만 허용하므로 div(flow content)는 불가
  • <a> 안에 <a>: interactive content 안에 interactive content 중첩 금지
  • <button> 안에 <input>: 마찬가지로 interactive content 중첩 금지
  • <ul> 바로 아래 <div>: ul의 콘텐츠 모델은 li만 허용 (script, template 제외)
<!-- ❌ 무효: p 안에 div -->
<p>문단 텍스트 <div>블록 요소</div> 끝</p>
<!-- 브라우저 실제 파싱 결과:
     <p>문단 텍스트 </p><div>블록 요소</div> 끝 -->

<!-- ✅ 유효: div 안에 p -->
<div>
  <p>문단 텍스트</p>
  <p>또 다른 문단</p>
</div>

<!-- ❌ 무효: a 안에 a -->
<a href="/outer">
  외부 링크 <a href="/inner">내부 링크</a>
</a>

<!-- ✅ 유효: 대안 구조 -->
<a href="/outer">외부 링크</a>
<a href="/inner">내부 링크</a>

W3C Validator 활용

Nu HTML Checker(validator.w3.org/nu)에 마크업을 입력하면 콘텐츠 모델 위반을 정확히 감지합니다. 예를 들어 <p><div>test</div></p>를 검사하면 “Element div not allowed as child of element p”라는 에러 메시지가 반환됩니다. CI/CD 파이프라인에 html-validate 같은 린터를 통합하면 배포 전에 자동으로 검증할 수 있습니다.

심화

중첩 규칙의 유효성 검증은 HTML 파서의 트리 구성 단계(tree construction stage)에서 실시간으로 수행되며, 명세에 정의된 알고리즘에 따라 위반이 감지되면 암묵적 요소 닫기(implied end tag)나 포스터 페어런팅(foster parenting) 같은 오류 복구 메커니즘이 작동합니다.

HTML 파서의 콘텐츠 모델 검증 메커니즘 HTML Living Standard Section 13.2 “Parsing HTML documents”에서 정의된 트리 구성 알고리즘(Tree Construction Algorithm)은 삽입 모드(Insertion Mode)라는 상태 머신(State Machine)으로 동작합니다. 각 삽입 모드는 현재 컨텍스트에서 허용되는 토큰(token) 유형을 정의하며, 허용되지 않는 토큰이 들어오면 명세에 정의된 오류 처리 절차를 따릅니다.

예를 들어, “In Body” 삽입 모드에서 <p> 태그가 열린 상태에서 <div> 시작 태그를 만나면, 파서는 열린 요소 스택(Stack of Open Elements)에서 p 요소가 스코프 내에 있는지 확인한 후, p 요소에 대해 암묵적 종료 태그를 생성합니다. 이 동작은 명세의 “has an element in button scope” 알고리즘을 통해 구현됩니다.

정적 분석 도구의 콘텐츠 모델 검증 접근법 html-validate, axe-core 같은 정적 분석 도구는 파서와 다른 접근법을 사용합니다. 이 도구들은 이미 파싱이 완료된 DOM 트리가 아니라, 소스 코드의 원본 마크업을 대상으로 콘텐츠 모델 규칙을 검사합니다. 이는 브라우저 파서의 오류 복구가 적용되기 전 상태를 검사하므로, 개발자의 의도와 명세 사이의 불일치를 정확히 포착할 수 있습니다.

특히 React나 Vue의 SSR(Server-Side Rendering) 환경에서는 하이드레이션 불일치(Hydration Mismatch) 문제가 발생할 수 있습니다. 서버에서 생성한 HTML이 콘텐츠 모델을 위반하면, 브라우저 파서가 오류 복구를 적용한 DOM 트리와 React가 기대하는 가상 DOM(Virtual DOM) 구조가 달라져 하이드레이션 에러가 발생합니다. React 18+에서는 이를 콘솔 경고로 보고하며, 심각한 경우 전체 클라이언트 사이드 리렌더링(Full Client-Side Re-render)으로 폴백합니다.

브라우저 오류 복구 메커니즘

입문

브라우저는 HTML에 실수가 있어도 화면을 보여주려고 최선을 다해요. 하지만 브라우저가 고치는 방식이 우리가 원하는 것과 다를 수 있어요.

🏗️ 건축가가 설계도를 고치는 것처럼 건축가에게 잘못된 설계도를 주면 어떻게 할까요? 그냥 안 짓겠다고 하지 않고, 나름대로 해석해서 건물을 지어요. 브라우저도 마찬가지예요. HTML에 잘못된 부분이 있어도 “이건 보여줄 수 없어요”라고 하지 않고, 스스로 고쳐서 화면에 보여줘요.

🎭 브라우저마다 고치는 방식이 달랐어요 옛날에는 같은 실수를 해도 크롬은 이렇게 고치고, 파이어폭스는 저렇게 고쳐서 브라우저마다 다르게 보이는 문제가 있었어요. 지금은 대부분 같은 규칙으로 고치지만, 그래도 처음부터 올바르게 작성하는 게 가장 안전해요.

🔧 자동으로 고쳐주는 예시 문단(p) 안에 큰 상자(div)를 넣으면, 브라우저는 “이건 문단 안에 들어갈 수 없네”라고 판단하고 문단을 먼저 닫아버려요. 그래서 우리가 의도한 구조와 실제로 만들어지는 구조가 달라져요.

⚠️ 눈에 보이는 결과가 맞다고 안심하면 안 돼요 화면에서는 멀쩡하게 보여도, 컴퓨터가 이해하는 구조는 우리가 의도한 것과 완전히 다를 수 있어요. 이렇게 되면 나중에 디자인을 바꾸거나 기능을 추가할 때 예상치 못한 문제가 생겨요.

중급

HTML 파서는 콘텐츠 모델을 위반하는 마크업을 만나도 파싱을 중단하지 않습니다. 대신 명세에 정의된 오류 복구(error recovery) 알고리즘을 실행하여 유효한 DOM 트리를 생성합니다. 이 과정에서 원본 마크업과 다른 DOM 구조가 만들어질 수 있습니다.

주요 오류 복구 동작

  1. 암묵적 닫기(Implied End Tag): 허용되지 않는 자식 요소를 만나면 현재 요소를 자동으로 닫음
  2. 포스터 페어런팅(Foster Parenting): table 요소 내부에서 허용되지 않는 요소를 table 바로 앞으로 이동
  3. 요소 무시(Ignoring Token): 특정 문맥에서 허용되지 않는 태그를 완전히 무시
<!-- 작성한 HTML -->
<p>단락 시작 <div>블록 요소</div> 단락 끝</p>

<!-- 브라우저가 생성한 실제 DOM -->
<!--
  <p>단락 시작 </p>
  <div>블록 요소</div>
   단락 끝
  <p></p>
-->

<!-- table 내부 포스터 페어런팅 -->
<table>
  <div>이 요소는 table 안에 있을 수 없습니다</div>
  <tr><td>셀</td></tr>
</table>

<!-- 실제 DOM: div가 table 앞으로 이동 -->
<!--
  <div>이 요소는 table 안에 있을 수 없습니다</div>
  <table>
    <tbody><tr><td>셀</td></tr></tbody>
  </table>
-->

개발자 도구로 확인하기

Chrome DevTools의 Elements 패널에서 실제 DOM 트리를 확인하면, 원본 소스 코드와 다른 구조를 볼 수 있습니다. 이 차이가 바로 오류 복구의 결과입니다. View SourceInspect Element의 차이를 비교하면 파서가 어떤 교정을 수행했는지 명확히 파악할 수 있습니다.

심화

HTML 파서의 오류 복구 메커니즘은 HTML Living Standard Section 13.2에서 수천 줄에 걸쳐 정의된 정교한 상태 머신 기반 알고리즘으로, 웹의 하위 호환성(Backward Compatibility)을 유지하면서 잘못된 마크업을 결정론적(deterministic)으로 처리합니다.

트리 구성 알고리즘의 오류 처리 설계 HTML 파서의 트리 구성 단계는 약 70개의 삽입 모드(Insertion Mode)로 구성된 상태 머신입니다. 각 삽입 모드는 해당 문맥에서 유효한 토큰 유형을 정의하고, 유효하지 않은 토큰에 대한 오류 복구 절차를 명시합니다.

“In Body” 모드에서 블록 레벨 시작 태그(div, h1-h6, ul 등)를 만났을 때의 처리 과정이 대표적입니다. 파서는 먼저 “has a p element in button scope” 알고리즘을 실행합니다. 열린 요소 스택(Stack of Open Elements)을 역방향으로 탐색하여, 스코프 경계 요소(applet, caption, html, table, td, th, marquee, object, template)에 도달하기 전에 p 요소를 발견하면, 해당 p 요소까지의 모든 요소를 스택에서 팝(pop)합니다. 이것이 “p 안에 div를 넣으면 p가 자동으로 닫히는” 동작의 구체적 구현입니다.

포스터 페어런팅의 구현 세부사항 포스터 페어런팅(Foster Parenting)은 table 관련 요소의 콘텐츠 모델 위반을 처리하는 특수 메커니즘입니다. “In Table” 삽입 모드에서 table, tbody, tr, td, th 이외의 요소를 만나면, 해당 요소를 “포스터 부모 요소(Foster Parent Element)“에 삽입합니다. 포스터 부모는 일반적으로 현재 table 요소의 바로 앞 형제 위치(preceding sibling position)입니다.

이 메커니즘은 1990년대 웹에서 table 레이아웃 시대에 흔했던 <table><div>...</div><tr>...</tr></table> 같은 패턴의 하위 호환성을 위해 설계되었습니다. 현대 브라우저(Blink, Gecko, WebKit)는 모두 동일한 명세를 구현하므로 동일한 결과를 생성하지만, 이 결정론적 동작 자체가 “올바른 렌더링”을 보장하는 것은 아닙니다. 오류 복구는 크래시 방지가 목적이지, 개발자 의도 보존이 목적이 아닙니다.

접근성 트리와 콘텐츠 모델의 관계

입문

시각장애인 분들은 웹페이지를 눈으로 보는 대신, 컴퓨터가 읽어주는 내용을 귀로 들어요. 이때 HTML의 구조가 올바르지 않으면 엉뚱한 내용을 듣게 될 수 있어요.

👂 누군가에게 책을 읽어주는 상황을 상상해보세요 여러분이 친구에게 책을 읽어준다고 해보세요. 책의 목차와 문단이 잘 정리되어 있으면 읽기도 쉽고 듣기도 쉽죠. 그런데 목차가 엉망이고 문단이 뒤죽박죽이면 읽어주는 사람도, 듣는 사람도 혼란스러워요. 웹페이지도 마찬가지예요.

🌳 눈에 보이지 않는 나무 구조 브라우저는 HTML을 분석해서 ‘접근성 트리’라는 보이지 않는 나무 구조를 만들어요. 스크린 리더(화면을 읽어주는 프로그램)는 이 나무를 따라가면서 내용을 읽어줘요. HTML 구조가 잘못되면 이 나무도 이상하게 만들어지고, 스크린 리더가 읽어주는 순서나 내용이 엉뚱해져요.

🚫 잘못된 구조의 실제 영향 예를 들어, 버튼 안에 또 다른 버튼을 넣으면 스크린 리더가 “버튼, 버튼”이라고 읽어줘요. 사용자는 어떤 버튼을 눌러야 하는지 알 수 없게 되죠. 이런 문제는 눈으로 화면을 볼 때는 발견하기 어렵지만, 스크린 리더를 사용하는 분들에게는 심각한 장벽이에요.

💡 올바른 HTML이 모두를 위한 웹을 만들어요 콘텐츠 모델을 지키면 자연스럽게 접근성도 좋아져요. 별도로 복잡한 접근성 작업을 하지 않아도, HTML 규칙만 잘 따르면 스크린 리더가 올바르게 내용을 전달할 수 있어요.

중급

브라우저는 DOM 트리를 기반으로 접근성 트리(Accessibility Tree)를 생성합니다. 스크린 리더, 점자 디스플레이 같은 보조 기술(Assistive Technology)은 이 접근성 트리를 통해 웹 콘텐츠를 해석합니다. 콘텐츠 모델 위반으로 DOM 구조가 왜곡되면, 접근성 트리도 함께 왜곡되어 보조 기술 사용자에게 잘못된 정보를 전달합니다.

콘텐츠 모델 위반이 접근성에 미치는 영향

  • 역할(Role) 혼란: 잘못된 중첩으로 요소의 ARIA 역할이 변경되거나 소실됨
  • 탐색 구조 파괴: 잘못된 heading/sectioning 중첩으로 문서 내비게이션이 불가능해짐
  • 인터랙션 모호성: interactive content 중첩으로 어떤 요소에 포커스가 가야 하는지 불분명해짐
<!-- ❌ button 안에 button: 접근성 트리에서 역할 충돌 -->
<button>
  저장
  <button>확인</button>
</button>
<!-- 스크린 리더: "버튼, 저장, 버튼, 확인" → 사용자 혼란 -->

<!-- ✅ 올바른 구조 -->
<div role="group" aria-label="저장 옵션">
  <button>저장</button>
  <button>확인</button>
</div>

<!-- ❌ ul 안에 div: 목록 탐색 단축키 작동 불가 -->
<ul>
  <div><li>항목 1</li></div>
  <div><li>항목 2</li></div>
</ul>

<!-- ✅ 올바른 구조 -->
<ul>
  <li>항목 1</li>
  <li>항목 2</li>
</ul>

WCAG와 콘텐츠 모델

WCAG 2.1의 성공 기준 4.1.1 “Parsing”(현재는 4.1.2 “Name, Role, Value”로 통합)은 마크업의 유효성을 요구합니다. 콘텐츠 모델 위반은 이 기준을 충족하지 못하게 하며, 접근성 감사(Accessibility Audit)에서 주요 결함으로 보고됩니다. Lighthouse나 axe-core 같은 도구로 자동 감지가 가능합니다.

심화

접근성 트리(Accessibility Tree)는 DOM 트리에서 시각적 표현(visual presentation) 정보를 제거하고 의미론적 구조(semantic structure)만을 추출한 병렬 데이터 구조로, 플랫폼별 접근성 API(MSAA/UIA on Windows, NSAccessibility on macOS, AT-SPI on Linux)를 통해 보조 기술에 노출됩니다.

DOM-to-Accessibility Tree 매핑 메커니즘 WAI-ARIA 명세와 HTML-AAM(HTML Accessibility API Mappings) 명세는 각 HTML 요소가 접근성 트리에서 어떤 역할(Role), 이름(Name), 상태(State), 속성(Property)으로 매핑되는지를 정의합니다. 이 매핑은 DOM 트리의 구조를 전제로 동작하므로, 오류 복구로 인해 DOM 구조가 변형되면 접근성 매핑도 영향을 받습니다.

예를 들어, <table> 내부에 유효하지 않은 요소가 포스터 페어런팅으로 이동하면, 해당 요소는 더 이상 table 역할의 자손이 아니게 됩니다. 스크린 리더의 테이블 탐색 모드(Table Navigation Mode)에서 이 콘텐츠에 접근할 수 없게 되며, 사용자는 존재하는 콘텐츠를 인식하지 못하게 됩니다.

Interactive Content 중첩의 접근성 파괴 메커니즘 HTML 명세에서 interactive content의 중첩을 금지하는 이유는 포커스 관리(Focus Management)와 활성화 동작(Activation Behavior)의 모호성 때문입니다. 접근성 API에서 각 interactive 요소는 고유한 actionable role을 가지며, 중첩 시 키보드 포커스 순서(Tab Order)와 활성화 이벤트(click, keydown Enter/Space) 전파 경로가 불확정적(non-deterministic)이 됩니다.

Chromium의 접근성 트리 구현(AXTree)에서는 중첩된 interactive content에 대해 내부 요소를 “ignored”로 처리하거나, 부모 요소의 accessible name에 병합하는 전략을 사용합니다. 그러나 이 동작은 명세에 정의되지 않은 구현별 행동(implementation-defined behavior)이므로, 브라우저 간 일관성이 보장되지 않습니다.