HTML and the DOM — Page Structure
Status: Active Last Updated: 2026-08-29 Category: Web — HTML Prerequisites: Web basics Tags: html, dom, semantic, accessibility, forms, a11y
Summary
HTML (HyperText Markup Language) describes the structure of a web page. The browser parses HTML into a DOM (Document Object Model) — a tree of nodes that JavaScript can manipulate. This article covers semantic HTML, the DOM structure, accessibility basics, and forms.
What You'll Learn
- HTML5 semantic elements
- The DOM tree and node types
- Accessibility: ARIA, semantic HTML, keyboard nav
- Forms: inputs, validation, submit behavior
Table of Contents
- Architectural Overview & Core Schema
- Deep Dive & Implementation
- Anti-Patterns & Common Pitfalls
- Independent Challenge
- Consolidation & Key Invariants
- Next Steps
1. Architectural Overview & Core Schema
Foundational theory, system mechanics, and high-level design.
HTML Structure
Every HTML page has the same skeleton:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Title</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>...</header>
<main>...</main>
<footer>...</footer>
<script src="app.js"></script>
</body>
</html>
The <head> contains metadata (title, character set, stylesheets). The <body> contains the visible content.
Semantic HTML5
HTML5 introduced elements that describe what content means, not just how it looks:
| Element | Purpose |
|---|---|
<header> |
Introductory content (page header, article header) |
<nav> |
Navigation links |
<main> |
Main content of the page |
<article> |
Self-contained piece (blog post, news article) |
<section> |
Thematic grouping of content |
<aside> |
Tangentially related content (sidebar) |
<footer> |
Footer content |
<h1> to <h6> |
Headings (only one <h1> per page) |
Using semantic elements helps:
- Screen readers navigate the page
- Search engines understand the content
- Styling and JavaScript become easier
2. Deep Dive & Implementation
Technical implementation, fully commented code blocks, and step-by-step logic.
Guided Checkpoint
Verify the DOM tree structure of a page in the browser console:
// Open DevTools Elements panel and run in console:
const body = document.querySelector('main');
console.log('Direct children:', body.children.length);
console.log('All child nodes:', body.childNodes.length);
console.log('First element child tag:', body.firstElementChild?.tagName);
console.log('Parent tag:', body.parentElement?.tagName);
// Navigate: get all <section> elements and inspect their hierarchy
document.querySelectorAll('section').forEach((sec, i) => {
console.log(`Section ${i + 1}: "${sec.id || '(no id)'}" — ${sec.children.length} children`);
});
Deep Dive — The DOM Tree
The DOM organizes nodes hierarchically: Document → <html> → <head> / <body>. Each tag is an Element node; text between tags is a Text node; comments are Comment nodes. Traversal uses parent, child, sibling links:
const nav = document.querySelector('nav');
nav.children; // HTMLCollection of direct child elements
nav.childNodes; // all children including text nodes
nav.firstElementChild; // skip text/comment
nav.parentElement; // go up
nav.nextElementSibling; // sibling
Deep Dive — Accessibility Basics
Accessibility (a11y) ensures content is perceivable, operable, understandable, and robust. Use semantic tags: <nav>, <main>, <article>, <section>, <aside>, <footer>, <header>. Add alt text to images describing content, not filenames. Label form inputs with <label for="id"> or wrap inputs inside <label>. Use aria-label, aria-describedby, aria-expanded, and role when semantics aren't enough.
Keyboard navigation: all interactive elements must be reachable via Tab (focusable by default: <a>, <button>, <input>). Use tabindex="0" to add custom widgets, tabindex="-1" to remove from tab order. Manage focus when opening modals (element.focus()). Test with a screen reader (NVDA, VoiceOver, TalkBack) and keyboard-only navigation.
Deep Dive — Forms and Validation
Forms collect user input and submit data. Every interactive control needs a name (sent with form) and id (linked by label). Use required, min, max, pattern, type="email", type="url" for native validation. Validate server-side — client-side validation is for UX only.
<form action="/signup" method="POST" novalidate>
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<button type="submit">Sign Up</button>
</form>
Handle submit events with addEventListener('submit', handler). Call event.preventDefault() to stop default navigation, gather data with new FormData(form), and send via fetch().
3. Anti-Patterns & Common Pitfalls
Documented failure modes and how to detect/prevent them.
Using <div> and <span> for everything (no semantics)
<div class="header"> and <span class="nav"> carry no meaning to screen readers or search engines. A <div> with class "nav" is invisible to assistive technology — a blind user cannot find the navigation landmarks. <span> for interactive elements removes keyboard focus by default.
Fix: Use semantic elements: <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>. Reserve <div> for pure layout grouping where no semantics apply.
Missing or meaningless alt text on images
alt="" (empty) is correct for decorative images. But alt="image1.jpg" or no alt attribute at all is harmful — screen readers announce the filename, and the image provides zero context. A missing alt on a meaningful image is a WCAG failure.
Fix: Write alt text that conveys the purpose of the image: what information does it convey, or what action does it enable? alt="Company logo" for a logo, alt="Screenshot of the dashboard" for a tutorial image.
Unclosed or unquoted HTML attributes
Omitting closing > on tags or leaving attribute values unquoted creates a lenient-browser-tolerates-it-but-wrongly-parsed situation. <input type=text required> may work, but <div class=my-class> followed by <div class="other"> can cause attribute bleed. Browsers apply error recovery that produces unpredictable DOM trees.
Fix: Always quote attribute values: type="text", class="card". Always close void elements: <img ... /> or <img ...> (both valid in HTML5). Use an HTML validator (W3C validator or the browser's built-in parser warnings) to catch these.
Forms without associated <label> elements
An <input> without a <label> is inaccessible: a sighted user sees a placeholder (which disappears when typing) but a screen reader has no accessible name for the input. The placeholder attribute is never a substitute for <label>.
Fix:
<!-- WRONG -->
<input type="email" placeholder="Enter email" />
<!-- CORRECT -->
<label for="email-input">Email address</label>
<input id="email-input" type="email" name="email" />
Relying on innerHTML with user-supplied content (XSS)
element.innerHTML = userInput parses userInput as HTML. Any <script> tag or inline event handler (onerror, onclick) embedded in userInput executes in the browser. This is a primary XSS attack vector.
Fix: Never pass untrusted input to innerHTML. Use textContent for plain text:
// Safe — treats userInput as literal text
const span = document.createElement('span');
span.textContent = userInput; // HTML chars escaped automatically
Mixing structure and style in HTML attributes
Using inline style attributes, width/height on <img>, or align attributes mixes presentation into structure. This makes CSS specificity issues worse and prevents reuse.
Fix: Keep all styling in CSS (class selectors, custom properties). Use CSS width/height on <img> via classes. Keep HTML for semantics only.
4. Independent Challenge
A problem to solve without step-by-step guidance; promotes synthesis.
Build an accessible, keyboard-navigable comment thread from scratch. Requirements:
- Write the HTML using only semantic elements — no
<div>for structural pieces - Each comment must have: author name, timestamp, body text, a "Reply" button, and a "Delete" button
- The "Delete" button must be keyboard-accessible and announce its action via
aria-label - Wire up the reply and delete buttons using event delegation on the container (not one listener per button)
- Add
requiredand appropriatetypeattributes to any form fields in the reply form - Validate that the page passes an accessibility audit (install the Axe DevTools browser extension and fix all reported issues)
Constraints: No frameworks. Pure HTML + browser JS. One event listener for all comments.
5. Consolidation & Key Invariants
Bullet-list summary of the must-remember rules.
- Use semantic elements (
<nav>,<main>,<article>,<header>,<footer>) — they provide meaning to assistive tech and search engines;<div>and<span>convey nothing. - Every form control needs a
<label>— theplaceholderattribute is not a label; screen readers and focus management depend on<label for>. textContentis the safe default for dynamic text —innerHTMLwith user input is an XSS vulnerability.- All interactive elements must be keyboard-accessible — use semantic elements (
<button>,<a>) or addtabindex="0"; non-interactive<div>s with click handlers exclude keyboard users. - Empty
alt=""for decorative images; meaningfulaltfor everything else — never leavealtoff a non-decorative image. - One
<h1>per page; headings must not skip levels — screen reader users navigate by heading; skipping from<h1>to<h3>confuses the document outline.
6. Next Steps
Sequenced links to dependent lessons, deeper dives, or production guides.
- Read
kb/web/css-basics.mdfor styling and layout - Read
kb/web/web-basics.mdfor rendering pipeline and HTTP - Practice DOM traversal and form validation in the browser console
- Review
kb/containers/docker-concepts.mdfor backend context
Change Log
- 2026-08-29: Migrated to 6-section canonical template (added sections 3, 4, 5; updated ToC; added Change Log entry)
- 2026-08-29: Expanded from stub to full entry-level lesson (deep dives, code examples, Next Steps, Change Log)