1. What HTML Is
HTML describes the structure and meaning of content. It's markup, not a programming language — no logic, no loops, no decisions. You wrap content in tags, and each tag says what that content is: a heading, a paragraph, a link, an image.
The "doing" comes from the other two languages. Think of it as a split:
- HTML — structure (the nouns)
- CSS — appearance (the adjectives)
- JavaScript — behavior (the verbs)
You'll spend this whole course on structure. Resist the urge to make things look a certain way here — "how do I make this red or centered?" is a CSS question, not an HTML one. Mixing them up is the most common beginner habit, and it makes both harder.
One thing worth knowing early: the browser doesn't render your HTML file directly. It reads the text and builds a tree of objects from it (the DOM), and everything after — CSS, JavaScript, accessibility — works on that tree. So HTML's real output isn't "a page," it's a structured document everything else stands on.
2. Document Structure
Every HTML page starts with the same skeleton:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Page Title</title>
</head>
<body>
<!-- everything visible goes here -->
</body>
</html>
What each part does:
<!DOCTYPE html>— tells the browser to use modern standards. Leave it out and the browser drops into "quirks mode" and emulates old bugs, breaking your layout in ways that are painful to debug.<html lang="en">— the root element.langmatters: screen readers use it to pick the right pronunciation, and browsers use it to offer translation. Uselang="ar"for Arabic pages.<head>— information about the page. Never shown in the content area.<body>— everything the user actually sees.
Trap: the DOCTYPE and lang cause no error if you forget them. The page still renders. You just quietly get quirks-mode layout bugs and worse accessibility, with nothing telling you why. Always include all four pieces.
3. The head
The <head> holds everything about the page that isn't shown in it:
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Site</title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/styles.css" />
<script src="/app.js" defer></script>
</head>
charset="UTF-8"— the character encoding. Get it wrong and Arabic, accents, and emoji turn into garbage likeالسلام. Put it first, before any text.viewport— tells phones to use the real device width instead of pretending to be a 980px desktop. Without this, your responsive site looks tiny and zoomed-out on mobile — this one line is the single most common reason a "responsive" page looks broken on a phone.<title>— the tab label, the bookmark name, and the blue headline in Google results.<link>— pulls in the favicon and CSS.<script>— pulls in JavaScript.
The defer trap: a <script> in the head without defer freezes page-building while it downloads and runs — the user stares at a blank screen. Add defer and it loads in parallel and runs after the HTML is ready. Rule: use <script defer> in the head, or put plain <script> at the end of <body>.
4. How the Browser Reads HTML
You write HTML as flat text. The browser reads it and builds a tree — this is the DOM (Document Object Model):
html
├── head
│ └── title
└── body
├── header
│ └── nav
└── main
└── article
├── h1
└── p
The tree comes from how your tags are nested, not from how you indent. A <p> inside an <article> is that article's child — and that relationship is what CSS and JavaScript later rely on to find and style things.
Two things follow from this:
- The DOM is not your HTML file. It's the live version the browser built. JavaScript can change it after load, so what you see in DevTools may differ from your source. When debugging, inspect the DOM (the Elements panel), not just the file.
- Broken nesting doesn't error — it gets silently "fixed." Forget a closing tag or overlap tags (
<b><i></b></i>) and the browser guesses a correction. Sometimes it guesses wrong, and you get a bug that makes no sense until you look at the actual DOM and see what the browser really built.
5. Elements, Nesting, Attributes
An element is an opening tag, content, and a closing tag:
<a href="/about" class="link">About</a>
<a>— opening taghref="/about",class="link"— attributes (they configure the element)About— the content</a>— closing tag
Void elements have no content and no closing tag — they're self-contained:
<img src="cat.jpg" alt="A cat" />
<br />
<hr />
<input type="text" />
<img>, <br>, <hr>, <input>, <meta>, <link> are the common ones.
Attributes come in a few flavors:
- Global (work on any element):
id,class,style,hidden,data-*. - Specific:
hrefon<a>,srcon<img>. - Boolean (true just by being present):
disabled,required,checked.
Trap: boolean attributes don't take ="true". Writing disabled="false" does not enable the element — the attribute being there at all is what disables it. To un-disable, remove it completely. This one confuses nearly everyone once.
Rule: nest cleanly, never overlap. <p><strong>ok</strong></p> is fine; <p><strong>bad</p></strong> breaks the tree.
6. Headings & Text
Headings create an outline of the page, like a table of contents:
<h1>Page Title</h1>
<h2>A Major Section</h2>
<h3>A Subsection</h3>
Three rules:
- One
<h1>per page — the main title. - Don't skip levels going down:
<h1>→<h2>→<h3>, not<h1>→<h4>. - Levels are structure, not size. If
<h2>looks too big, shrink it with CSS — don't drop to<h4>just because its default size looks right.
This isn't pedantry: screen-reader users navigate by pulling up a list of headings and jumping between them. A broken outline breaks that navigation. It also affects SEO — search engines read your heading structure to understand the page.
For prose:
<p>— a paragraph.<br>— a line break inside text. Use it only where the break is part of the content (an address, lyrics) — never to add space between paragraphs.<hr>— a thematic break (a shift in topic), shown as a divider line.
Trap: <br><br> to create spacing between blocks. That's a CSS margin job. Using breaks for spacing is a classic beginner tell and it fights you the moment you add CSS.
7. strong/em vs b/i
These two pairs look identical in the browser but mean different things — and the difference is audible to screen readers:
| Tag | Looks | Means |
|---|---|---|
<strong> |
bold | This is important |
<b> |
bold | Just draw the eye, no added meaning |
<em> |
italic | Stressed emphasis (changes the sentence's meaning) |
<i> |
italic | A term, a name, foreign words |
Why it's not just trivia: a screen reader can change its tone for <strong> and <em> because they carry meaning — it emphasizes them out loud. It reads <b> and <i> flatly, because they're only visual.
So "I said <strong>do not</strong> touch it" conveys the urgency to a blind user. The same sentence with <b> loses it.
The rule: use <strong>/<em> when the content genuinely matters or is stressed. Use <b>/<i> only for visual distinction with no added meaning (a book title in italics is <i> — it's a title, not emphasis). When unsure, pick the semantic pair.
8. Lists
Three list types, each with its own meaning:
<!-- Unordered — order doesn't matter -->
<ul>
<li>Milk</li>
<li>Eggs</li>
</ul>
<!-- Ordered — sequence is the point -->
<ol>
<li>Preheat the oven</li>
<li>Mix the batter</li>
</ol>
<!-- Description — term + definition pairs -->
<dl>
<dt>HTML</dt>
<dd>The structure of a page</dd>
</dl>
<ul>— bullets. Use when order is irrelevant (features, nav links, a shopping list).<ol>— numbers. Use when order matters (steps, rankings).<dl>— term/description pairs (glossaries, key-value data, FAQs).
Real-world note: a navigation menu is a <ul> of <a>s inside a <nav> — not a pile of divs. This is the standard pattern you'll see everywhere, and it's why: a screen reader announces "list, 5 items" and lets the user step through them. Divs give none of that.
Rules:
- Every direct child of
<ul>/<ol>must be an<li>— you can't put a<div>straight inside a<ul>. - Lists can nest: an
<li>can hold another<ul>.
9. Semantic HTML
Semantic elements have names that describe what they are: <header>, <nav>, <article>, <footer>. Non-semantic elements describe nothing — <div> and <span> are just boxes.
These two render exactly the same:
<div class="header">...</div>
<header>...</header>
So why use the semantic one? Because <div> is meaningless to everything except your eyes — and a lot of what happens to your page is done by things that aren't your eyes:
- Screen readers let blind users jump straight to
<nav>or<main>. Divs give them nothing to jump to. - Search engines read
<article>and<h1>as real content. A<div class="title">is nothing to Google. - Reader mode and translation tools find
<article>and<main>to work.
This is really an SEO and accessibility topic in disguise — two things a real job measures you on. You get both for free just by picking the right tag.
The main tags
| Tag | Use it for |
|---|---|
<header> |
Top of a page or a section |
<nav> |
Navigation links |
<main> |
The main content — one per page |
<article> |
A self-contained piece: a post, a card, a comment |
<section> |
A group of related content, usually with a heading |
<aside> |
Side content: a sidebar, related links |
<footer> |
Bottom of a page or a section |
A full page:
<body>
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<article>
<h1>My Post</h1>
<p>The content...</p>
</article>
<aside>Related links</aside>
</main>
<footer>© 2026</footer>
</body>
The two decisions people get wrong
article vs section. Ask: if I pulled this out and put it on its own page, would it still make sense? Yes, it stands alone → <article> (a blog post, a product card). No, it's just a themed chunk of a bigger page → <section> (a "Related Products" block).
header/footer aren't page-only. The names sound like "top and bottom of the site," but they mean the top and bottom of their nearest section. An <article> can have its own:
<article>
<header>
<h2>Post Title</h2>
<p>Published today</p>
</header>
<p>The content...</p>
<footer>Written by Karim</footer>
</article>
Traps
<div onclick>faking a button. Looks clickable, but it's not focusable, ignores the Enter key, and screen readers don't announce it as a button. → Navigates? Use<a>. Does something? Use<button>.- More than one
<main>. Exactly one per page. <section>as a styling wrapper. A<section>should have a heading. No heading and only grouping for CSS? You wanted a<div>.
Tip: before writing <div>, ask if there's a tag that describes what it is. <div> is the fallback, not the default.
10. div and span
These are the two meaningless containers, for when nothing semantic fits:
<div>— a generic block box. Full width, stacks vertically. Use it to group elements for layout or styling.<span>— a generic inline box. Sits inside a line of text. Use it to target part of some text.
<div class="card">
<p>Price: <span class="highlight">$40</span></p>
</div>
They aren't villains — they're the right choice when the only reason the element exists is styling or grouping, with no meaning to express (a flex container, a card wrapper, a colored word). Semantic HTML just says use them last, after checking no meaningful tag fits.
Trap: using <div> mid-sentence. It's block, so it breaks onto its own line. To style one word inside text, you want <span> (inline). Picking the wrong one here is the usual reason "my text jumped to a new line for no reason."
11. Links
<a href> is what makes the web a web. Every navigation is an anchor:
<a href="https://example.com">External site</a>
<a href="/about">Internal page</a>
<a href="#section-2">Jump within this page</a>
<a href="mailto:me@site.com">Email</a>
<a href="tel:+201234567890">Call</a>
- Absolute (
https://...) — a full address to another site. - Relative (
/about) — a path within your own site. - Fragment (
#id) — jumps to the element with thatidon the current page.
The security trap everyone hits: opening a link in a new tab.
<a href="https://x.com" target="_blank" rel="noopener noreferrer">Opens safely</a>
target="_blank" opens a new tab — but without rel="noopener noreferrer", the new page can reach back into your page's window object. That's a real security and performance hole. Always pair target="_blank" with rel="noopener noreferrer".
Accessibility that also helps SEO: link text must make sense on its own. Screen-reader users pull up a list of every link on the page — a list of "click here, click here, read more" is useless. Write See our pricing, not click here.
Rule of thumb: if it navigates to a URL, it's an <a>. If it performs an action, it's a <button>. Don't use links as buttons.
12. Images
<img src="/photos/cat.jpg" alt="A ginger cat asleep on a keyboard" width="600" height="400" />
src— the file path.alt— the text alternative. Read by screen readers, shown if the image fails to load, indexed by search engines.width/height— the real dimensions. Setting them lets the browser reserve the space before the image loads, so the page doesn't jump around as images pop in.
Writing alt correctly — this is the part people get wrong:
- Meaningful image → describe it:
alt="Bar chart showing sales doubling in Q4". - Decorative image (adds nothing) → empty
alt="". This tells the screen reader to skip it. Empty is correct — missing is not. - Never write
alt="image"oralt="photo of a cat"— screen readers already say "image," so you'd get "image, image of a cat." Just describe: "a cat."
The test: if the image vanished, what text would keep the meaning? That's your alt.
Trap: skipping width/height. No error, but every image causes layout shift — the page visibly jumps as images load, which is jarring and hurts your performance score. loading="lazy" also helps for images far down a long page (but not the hero image — you want that one immediately).
13. Responsive Images
A single <img src="huge.jpg"> sends the same file to everyone. A phone on mobile data downloading a 3MB desktop image is slow and wasteful. Responsive images let the browser pick the right file.
srcset — same image, different sizes:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A landscape" />
srcset lists the file options with their real widths; sizes tells the browser how wide the image will actually display. The browser does the math and downloads the smallest file that still looks sharp. src is the fallback for old browsers.
<picture> — genuinely different images (art direction):
<picture>
<source media="(max-width: 600px)" srcset="square.jpg" />
<source media="(min-width: 601px)" srcset="wide.jpg" />
<img src="wide.jpg" alt="Our team" />
</picture>
Use <picture> when you want a different crop per screen (a wide banner on desktop, a tight square on mobile), or to serve modern formats (WebP) with a JPEG fallback.
The decision: same picture, just right-sized for the device → srcset. Different crops or formats → <picture>.
Trap: <picture> still needs an <img> inside it — that's the element that actually renders. Forget it and nothing shows.
14. Video & Audio
<video> and <audio> play media natively — no plugins:
<video controls width="640" poster="thumb.jpg">
<source src="clip.webm" type="video/webm" />
<source src="clip.mp4" type="video/mp4" />
<track kind="captions" src="captions.vtt" srclang="en" label="English" />
Your browser doesn't support video.
</video>
controls— shows play/pause/volume. Without it, there's no way to play the media — the single most common mistake here.poster— the thumbnail before playback.<source>— offer multiple formats; the browser picks the first it supports.<track>— captions. A must for any video with speech, and often a legal requirement.
Audio is the same model with no picture:
<audio controls>
<source src="song.mp3" type="audio/mpeg" />
</audio>
Trap: autoplay on its own is silently blocked by every modern browser. If you want a video to autoplay, it must also be muted. Browsers block autoplaying sound on purpose — nobody wants a page shouting at them.
15. Block vs Inline
Every element has a default behavior before any CSS touches it:
- Block — starts on a new line, takes the full width, and stacks.
<div>,<p>,<h1>–<h6>,<section>,<ul>,<li>. - Inline — flows within a line of text, taking only as much width as its content.
<span>,<a>,<strong>,<em>,<img>.
<p>This paragraph is a block.
<a href="#">This link</a> and <strong>this text</strong> are inline —
they sit in the line without breaking it.</p>
This is why wrapping one word in a <div> shoves it onto its own line (block), while <span> keeps it in place (inline). Knowing the default tells you which container to reach for.
The CSS connection (a preview): CSS can override this with the display property — you can make a block element inline or vice versa. But every element starts with a sensible default, and knowing those defaults is what makes CSS layout predictable later. A lot of "why won't width work on my <span>?" confusion comes from not knowing it's inline by default.
16. data-* Attributes
Sometimes you need to attach your own data to an element — a product ID, a state, a config value — that isn't any standard attribute. data-* lets you invent attributes safely:
<button data-product-id="42" data-in-stock="true">Add to cart</button>
Any attribute starting with data- is valid and ignored by rendering. It's there purely for you to read from JavaScript:
const btn = document.querySelector('button');
btn.dataset.productId; // "42" (data-product-id → dataset.productId)
btn.dataset.inStock; // "true"
The dataset API converts data-product-id to dataset.productId automatically (kebab-case becomes camelCase).
Why this is worth knowing: this is the clean, sanctioned way to pass data from server-rendered HTML to your JavaScript. The messy alternatives people reach for instead — hiding values in class names, or in invisible elements — are exactly what data-* exists to replace. When an event handler needs to know "which product was clicked?", the answer lives in a data-* attribute.
Don't use it to reinvent id (that's for identity), and don't store huge JSON blobs in it — it's for small values.
17. Entities & Comments
Some characters are part of HTML's syntax — <, >, and &. To show them as literal text, use entities:
| You want | You write |
|---|---|
< |
< |
> |
> |
& |
& |
| non-breaking space | |
| © | © |
<p>Use <div> for grouping.</p> <!-- shows: Use <div> for grouping. -->
Without entities, the browser tries to parse <div> as a real tag instead of showing it. keeps two words on the same line (10 km) — but don't use it for spacing; that's CSS.
Comments are ignored by the browser:
<!-- A note to yourself -->
<!-- <p>Temporarily disabled by commenting out</p> -->
Trap worth flagging: HTML comments are fully visible in "View Source." Never put anything sensitive in them — no API keys, no passwords, no private notes. They ship to every visitor's browser.
18. Accessibility Basics
Accessibility (a11y) means building pages usable by people who don't see, hear, or navigate the way you do — screen-reader users, keyboard-only users, people using magnification.
The good news: you've already been doing most of it. If your HTML is honest — semantic tags, alt on images, a clean heading outline, real lists — you get the majority of accessibility for free. This section just names the pieces:
- Semantic structure — landmarks (
<header>,<nav>,<main>), correct headings, real lists. alton images — meaning in text, empty for decorative.- Labels on inputs — every form input needs a
<label>(covered in Session 1.2). - Keyboard operability — everything interactive must work without a mouse. Real
<a>and<button>get this automatically;<div onclick>does not. - Visible focus — keyboard users need to see where they are. Don't remove focus outlines without replacing them.
The test: if you closed your eyes and only heard the page read aloud, top to bottom — would it still make sense, and could you still operate it?
Why a real job cares: accessibility is a legal requirement in many markets, and it overlaps heavily with SEO — the same semantic structure that helps a screen reader helps Google. It's not a "nice to have" you add at the end.
Trap: color as the only signal. "Errors are shown in red" fails colorblind users. Pair color with text or an icon, always.
19. ARIA
ARIA is a set of attributes (role, aria-label, aria-hidden, and others) that add accessibility information when HTML alone can't express it:
<button aria-label="Close">✕</button> <!-- icon-only button gets a name -->
<div role="alert">Your changes were saved.</div> <!-- announce this immediately -->
<span aria-hidden="true">🔥</span> <!-- hide decoration from screen readers -->
role— declares what something is when its tag doesn't say (role="alert").aria-label— gives a name when there's no visible text (an icon button).aria-hidden="true"— hides purely decorative content from screen readers.
The one rule that matters most:
No ARIA is better than bad ARIA. Use a native element before reaching for a role.
A real <button> is already announced as a button, is keyboard-operable, and is focusable. <div role="button"> forces you to rebuild all of that by hand with JavaScript — and people usually get it wrong, ending up with something less accessible than if they'd just used <button>.
So ARIA is for the genuine gaps native HTML can't fill (a custom tab widget, a live-updating region), not a substitute for the right tag. Most pages need very little of it.
20. Meta Tags & SEO
<meta> tags in the <head> tell search engines how to treat your page:
<title>Clear, Specific Page Title | Site Name</title>
<meta name="description" content="A 150-character summary shown in search results." />
<link rel="canonical" href="https://site.com/the-real-url" />
<meta name="robots" content="index, follow" />
<title>— the single biggest on-page SEO signal, and the blue clickable headline in Google. Make it specific.description— the grey snippet under the title. Doesn't directly rank you, but a good one raises click-through. Around 150–160 characters.canonical— when the same content lives at multiple URLs, this points to the "real" one so search engines don't split your ranking or flag duplicate content.robots—index/noindex(should this page show in results?) andfollow/nofollow(should its links pass weight?).
The honest framing: SEO is a big field, but at the HTML level it's simple — a specific <title>, a useful description, clean semantic structure, fast responsive images, and honest canonical/robots signals. HTML doesn't trick search engines; it makes the page legible to them.
Trap that can wipe you off Google: a stray noindex left in by accident removes the page from search entirely. It causes no visible problem on the page itself — you just silently disappear from results. Also: don't reuse one generic <title> ("Home") across every page; each needs a distinct one.
21. Open Graph
Paste a link into WhatsApp, LinkedIn, or Slack and you get a rich preview — a title, description, and image. That doesn't come from your <title>; it comes from Open Graph (og:) tags:
<meta property="og:title" content="How the Web Works" />
<meta property="og:description" content="A beginner's guide to requests and responses." />
<meta property="og:image" content="https://site.com/preview.jpg" />
<meta property="og:url" content="https://site.com/how-the-web-works" />
<meta property="og:type" content="article" />
og:title/og:description— the preview's headline and text (can differ from your SEO title).og:image— the preview image, and the one that matters most. A good one dramatically raises clicks. Aim for ~1200×630px.og:url— the page's canonical URL.
For X/Twitter there's a parallel set (twitter:card, etc.), though most platforms fall back to og: tags.
Why bother: the difference between a link that shares as a rich, clickable card and one that shares as a blank grey box is entirely these tags. On social and messaging, that's the difference between people clicking and scrolling past.
Trap: no og:image, and your carefully-built page shares as an ugly text-only box. People don't click those.
22. Validation
The browser is forgiving — forget a closing tag, nest illegally, or miss a required attribute, and it silently patches your mistake and renders something anyway. That forgiveness is the problem: your page looks fine while its structure is quietly broken, and it surfaces later as a mysterious CSS or JavaScript bug.
A validator (validator.w3.org) reads your HTML strictly and tells you exactly what's wrong:
- Unclosed or mismatched tags
- Invalid nesting (a
<div>inside a<p>, an<li>outside a list) - Missing required attributes (
<img>with noalt) - Duplicate
ids — anidmust be unique per page; duplicates breakgetElementByIdand label/input binding
Why it earns its place: most "it works but something's weird" bugs trace back to a validation error you never saw, because the browser hid it by guessing. Paste your URL or source into the validator, or use an editor extension that checks as you type.
The mindset to take from this whole session: "it renders" is not "it's correct." The browser will happily render broken HTML. The validator is how you catch the difference before it becomes a bug you can't explain.