Command Palette

Search for a command to run...

Hectal
PHASE 7Advanced ~22 min· topic 7 of 8

Topic 7.7

HTML Best Practices and Common Mistakes

In one line

Good HTML picks the element that matches the meaning, keeps structure and order logical, avoids deprecated and fake controls, and stays lean for speed; these are the mistakes reviewers spot first.

Think of it like this

A well-organised kitchen. Knives in the knife block, spoons in the spoon drawer, labels on every jar. Anyone, even a guest, can cook there. A kitchen where everything is thrown into identical unlabelled boxes still "contains" all the tools, but nobody can find anything. A page built only from divs is that kitchen.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Div soup
HTML made almost only of divs and spans, with no meaningful elements.
Semantic HTML
Choosing the element that matches the meaning of the content: nav for navigation, button for actions.
Deprecated element
An old element the HTML standard no longer allows, like <center> or <font>.
Void element
An element that can't have content or an end tag, like <img> or <br>.
Layout shift
Content jumping on screen as images or ads load in. Measured as CLS (Cumulative Layout Shift).
Content Security Policy (CSP)
A security header that tells the browser which scripts and styles it may run; strict policies block inline ones.
Link text
The clickable words of a link. It should say where the link goes, even out of context.

Step by step

01Start from div soup

This page renders, but the accessibility tree has no landmarks, no heading, no list and no buttons. A screen reader user hears a long run of text with "clickable" items they can't reach with Tab.

before.htmlwhole filehtml
<div class="header">
  <div class="logo">Bake Club</div>
  <div class="menu">
    <div class="item" onclick="go('/')">Home</div>
    <div class="item" onclick="go('/recipes')">Recipes</div>
  </div>
</div>
<div class="content">
  <div class="big-title">Easy bread</div>
  <div class="text">Four ingredients, one bowl.</div>
  <div class="btn" onclick="save()">Save recipe</div>
</div>
<div class="footer">© 2026 Bake Club</div>

02Name the regions

Swap the outer wrappers for landmark elements. Each becomes a jump point in screen readers' landmark list.

Name the regionsdiagram
Rendering diagram…

03Use real links, headings and buttons

Navigation items go to pages, so they're links in a list. The title is the page's h1. Saving is an action, so it's a button. No click handlers on divs are left.

after.htmlwhole filehtml
<header>
  <a href="/" class="logo">Bake Club</a>
  <nav aria-label="Main">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/recipes" aria-current="page">Recipes</a></li>
    </ul>
  </nav>
</header>
<main>
  <article>
    <h1>Easy bread</h1>
    <p>Four ingredients, one bowl.</p>
    <button type="button" id="save">Save recipe</button>
  </article>
</main>
<footer><p>© 2026 Bake Club</p></footer>

04Replace deprecated markup with CSS

Looks belong in CSS. The result is shorter, valid and easy to restyle.

old-vs-new.htmlwhole filehtml
<!-- old -->
<center><font color="red" size="5">Sale!</font></center>
<table border="1" bgcolor="#eee" align="center">…</table>

<!-- new -->
<p class="sale">Sale!</p>
<table class="data">…</table>
<style>
  .sale { text-align: center; color: #b91c1c; font-size: 1.5rem; }
  .data { margin-inline: auto; border-collapse: collapse; background: #eee; }
  .data td, .data th { border: 1px solid #999; }
</style>

05Make it fast

Reserve space for images, defer scripts, and load only what's above the fold eagerly.

head-and-images.htmlwhole filehtml
<head>
  <link rel="stylesheet" href="/site.css">
  <script src="/app.js" defer></script>
</head>
…
<img src="hero.avif" alt="Fresh loaf on a wooden board." width="1200" height="600" fetchpriority="high">
<img src="step-3.webp" alt="Dough after the first rise." width="600" height="400" loading="lazy">

06Review with a checklist

Before merging HTML, check: one clear h1 and no skipped levels; landmarks present; every image has a deliberate alt; every field has a label; links go places, buttons do things; no deprecated elements; validator and axe clean; images sized; scripts deferred.

Try it yourself

  1. 1

    Fix the trap

    In the first example, change <div class="card" /> to <div class="card"></div>. Predict the three log lines, then run. (false, (none), 0.)

  2. 2

    Turn soup into structure

    In the third example, change only the soup section until both log lines match, using a heading, a button and a list. Notice you can keep the classes for styling.

  3. 3

    Replace the old tags

    In the second example's HTML, replace <center> and <font> with a <p class="note"> and add .note { text-align: center; color: red; } to the CSS. Same look, valid HTML.

Code & diagrams

The self-closing div trap New tab

The dashed border wraps everything: the browser ignored the slash and the div stayed open until the end of the body. It even swallowed the `<script>` that runs this example. Write `<div class="card"></div>`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

p inside .card? true
children of .card: P, IMG, P, SCRIPT
img has children? 0
What the browser makes of old and unknown tags New tab

`center`, `font` and `marquee` are obsolete but still supported for old pages; `blink` is gone entirely. All four are invalid in modern HTML. Use CSS for looks.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

center -> HTMLElement
font -> HTMLFontElement
marquee -> HTMLMarqueeElement
blink -> HTMLUnknownElement
button -> HTMLButtonElement
Div soup versus semantic HTML, as assistive tech sees it New tab

The two halves look alike. Only one has structure a screen reader can navigate: "Easy bread, heading level 2", "Save recipe, button", "list, 4 items".

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

soup: 0 heading, 0 button, 0 list
semantic: 1 heading, 1 button, 1 list

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Use a link as a button

<a href="#" onclick="openCart()">Cart</a>

terminal
$ click, then press Space on it, then middle-click
── what you'll see ──
The page scrolls to the top and the URL gains a #; Space scrolls the page instead of activating; a new tab opens the same page

Break #2

Choose headings by size

Use <h4> for a sidebar title because it "looks the right size", right after the page's h1.

terminal
$ screen reader headings list
── what you'll see ──
Easy bread, heading level 1 → Related recipes, heading level 4 (levels 2 and 3 missing)

Break #3

Lazy-load the hero image

Add loading="lazy" to the large image at the top of the page.

terminal
$ Lighthouse → Performance
── what you'll see ──
Largest Contentful Paint image was lazily loaded

Myth vs fact

Myth

<div /> is a valid way to write an empty div.

Fact

In HTML the slash is ignored on non-void elements, so the div stays open. Only void elements like <img> and <br> are written without an end tag.

Myth

Semantic HTML is only for SEO.

Fact

It also gives keyboard support, screen reader navigation, reader modes, translation tools and cleaner CSS hooks, all for free.

Myth

<b> and <i> are deprecated.

Fact

They're valid HTML with defined meanings (text stylistically offset, alternate voice, like a ship's name or a foreign phrase). Use <strong> and <em> when you mean importance or stress emphasis.

Myth

If it works in my browser, it's fine.

Fact

Test with the keyboard, a screen reader, a phone, zoom, and the validator. "Works" means works for everyone.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Modern browsers treat target="_blank" on links as if rel="noopener" were set (Chrome, Firefox and Safari since about 2021), so the new page can't control yours via window.opener. Adding rel="noopener" explicitly is still harmless and protects very old browsers. Tell users a link opens a new tab, in the text or an icon with a text alternative.

  • ▸

    Each DOM element costs memory and work during style recalculation and layout; deep nesting makes selector matching and layout slower. Lighthouse flags an "excessive DOM size" above about 1,400 elements on the page, with a deeper warning for depth over 32 and parents with more than 60 children.

  • ▸

    A strict CSP (script-src 'self' without 'unsafe-inline') blocks inline onclick attributes and <script> blocks; style-src without 'unsafe-inline' blocks style="…". Writing HTML without inline handlers from the start makes adopting CSP (a key XSS defence, see Topic 5.5 and the System Design course) painless.

  • ▸

    Custom elements (<my-card>) are valid when the name contains a hyphen; unknown names without a hyphen become HTMLUnknownElement. Custom elements have no semantics by default, so give them real HTML inside or use the ElementInternals API to set a role.

Remember this

  1. 1

    Div soup is a page built almost entirely from <div> and <span> with classes. It renders, but it has no landmarks for screen reader users to jump between, no headings to skim, no buttons for keyboard users, and nothing for search engines to understand. Use header, nav, main, article, section (with a heading), aside, footer, real headings, lists for lists, button for actions and a for navigation (Phase 2). Use div only when no element has the right meaning, usually for styling wrappers.

  2. 2

    Links versus buttons: a link (<a href>) goes somewhere (a new page or a place in this page); a button does something (submits, opens, toggles, deletes). Links respond to Enter, can be opened in new tabs, and appear in screen readers' link lists; buttons respond to Enter and Space. <a href="#" onclick="…"> and <a href="javascript:void(0)"> are buttons pretending to be links: the page jumps to the top, "open in new tab" opens nonsense, and Space doesn't work. Link text must make sense alone: "Download the 2024 report (PDF, 2 MB)", never "click here".

  3. 3

    Structure mistakes: skipping heading levels or choosing headings by font size (pick the level by structure, style it with CSS); several h1s fighting each other; missing lang, charset or title; duplicate ids; missing labels and alt (Topics 7.2 and 7.5); <br><br> for spacing (use CSS margin); tables for layout (Topic 3.4); empty elements for decoration (use CSS); text in images.

  4. 4

    Deprecated and obsolete elements and attributes still appear in old tutorials and copied code: <center>, <font>, <big>, <tt>, <strike>, <acronym>, <marquee>, <blink>, <frame>/<frameset>, and presentational attributes like align, bgcolor, border on tables and width on table cells. Some still render for backwards compatibility, but they're non-conforming (the validator flags them), mix looks into content and have accessible replacements: CSS for looks, <abbr> for <acronym>, <s> or <del> for <strike>.

  5. 5

    Self-closing myths: only void elements (img, br, hr, input, meta, link, source, track, wbr, area, base, col, embed) have no end tag. In HTML, the slash in <div /> is ignored: it opens a div that swallows everything after it. JSX and XML allow <div />; HTML does not. Also: attributes are lowercase by convention, quote attribute values, and don't put block elements inside inline ones like p or span.

  6. 6

    Performance is part of quality: give images width and height to prevent layout shift (Topic 5.2), lazy-load below-the-fold images (loading="lazy") but never the main hero image, use modern formats and srcset, load scripts with defer or type="module" (Topic 6.5), keep the DOM small (Lighthouse warns at around 1,400 elements; every node costs memory, style and layout time), avoid inline SVG repeated hundreds of times (use <use> or an image), and don't load fonts and libraries you don't use. Inline style="…" and onclick="…" attributes also block a strict Content Security Policy (a security header that blocks unexpected scripts), so keep styles in CSS files and handlers in JavaScript.

Explain it without notes

01

What is div soup, and why is it a problem beyond accessibility?

02

When should you use a link and when a button?

03

Why is <div /> a bug in HTML but fine in React?

04

Name five HTML habits that improve performance.

Practice

01

Rewrite this as semantic HTML: <div class="nav"><span onclick="location='/'">Home</span> <span onclick="location='/blog'">Blog</span></div><div class="h">My Blog</div>.

02

Find and fix four mistakes: <img src="chart.png"><a href="report.pdf">Click here</a><br><br><br><font size="2">Updated 2024</font>.

Trade-offs

  • ↔

    Strict semantic HTML sometimes needs extra CSS resets (buttons and lists have default styles), but that small CSS cost is far cheaper than rebuilding focus, keyboard and roles in JavaScript.

  • ↔

    Deleting every legacy element at once on a big old site risks visual regressions; replace them gradually, page template by page template, with visual tests.

Done when you can

  • I can turn div soup into landmarks, headings, lists, links and buttons.

  • I choose links for navigation and buttons for actions.

  • I avoid deprecated elements and presentational attributes.

  • I know which elements are void and never write <div /> in HTML.

  • I size images, defer scripts and keep the DOM lean.

  • I can review HTML with a checklist before merging.