Writing Meaningful HTML: 7 Semantic HTML Mistakes Almost Everyone Makes

They look identical on screen, but a div button, an input without a label, or a heading picked by font size look completely different to keyboard users, screen readers, and search engines. Common semantic HTML mistakes and how to fix them, with examples you can try.

They look the same, so what's different?

The preview below has two cards. One is built entirely from divs; the other uses tags that match their meaning. They look exactly the same. Compare the code in the html tab below.

Both work fine with a mouse. But click once inside the preview and press Tab. Focus only goes to the button in the second card. The div button can't be reached from the keyboard at all.

Semantic HTML means choosing tags by "what it is," not "how it looks." CSS handles appearance; tags handle meaning.

To someone looking at the screen there's no difference. To anything that reads the HTML, the difference is big.

Who reads your HTMLWhat meaningful tags give them
Keyboard usersThey can Tab between buttons, links, and inputs
Screen readersThey announce roles like "button," "heading level 2," "navigation"
Search enginesThey can tell main content, headings, and navigation apart
BrowsersReader mode, auto-translate, and autofill work better
The next developerheader reads faster than div class="header"

Let's go through the mistakes you see most often in real code.


Mistake 1. A clickable div

The most common mistake, and the one with the biggest impact.

<!-- ❌ -->
<div class="btn" onclick="save()">Save</div>

<!-- ✅ -->
<button type="button" onclick="save()">Save</button>

Faking a button properly with a div takes all of this:

  • tabindex="0" so it can receive keyboard focus
  • role="button" so screen readers know it's a button
  • Handling the Enter and Space keys yourself
  • Implementing a disabled state yourself

A button tag gives you all of it by default. Click inside the preview below, move focus with Tab, and press Enter.

A button inside a form defaults to type="submit". If the button shouldn't submit the form, always write type="button", or every press will submit it.


a and button are both "things you press," so they're easy to confuse, but the rule is simple.

If it takes you somewhere else, it's an a. If it does something on the current page, it's a button.

<!-- ❌ Using a link for something that doesn't navigate -->
<a href="#" onclick="openModal()">Log in</a>

<!-- ✅ It acts on the current page, so it's a button -->
<button type="button" onclick="openModal()">Log in</button>

<!-- ❌ Using a button to navigate -->
<button onclick="location.href='/pricing'">See pricing</button>

<!-- ✅ It navigates, so it's a link -->
<a href="/pricing">See pricing</a>

Turn a link into a button and open in new tab, copy link address, and search engine crawling all stop working. Use an href="#" link as a button and the page jumps to the top or the URL gets a # tacked on.


Mistake 3. Picking heading tags by font size

"This needs to be small, so h4" is a very common choice. But h1–h6 build the document's outline. Screen reader users skim pages by listening to headings only.

<!-- ❌ Picked by size, so the levels are all over the place -->
<h1>Shop</h1>
<h4>Today's picks</h4>
<h2>Product name</h2>

<!-- ✅ Follow the structure, size with CSS -->
<h1>Shop</h1>
<h2 class="section-title">Today's picks</h2>
<h3>Product name</h3>
  • It's safest to have a single h1 describing the page's topic.
  • Don't skip levels (h2 straight to h4 ❌).
  • To change the size, change the CSS, not the tag.

Mistake 4. Inputs without a label

People often drop the label and rely on placeholder because it looks cleaner. In the preview, click the text next to each checkbox.

The first checkbox, with no label, only checks if you hit the tiny box exactly. With a label, clicking the text checks it too, and clicking the word Email puts the cursor straight into the input. On mobile the difference is even bigger.

Why placeholder alone isn't enough:

  • It disappears once you start typing, so you forget what the field was for.
  • Its faded color is hard to read.
  • Screen readers don't always announce it as the field's name.

If the design calls for no visible label, don't delete it. Hide it visually with CSS instead (a class commonly named sr-only). Sighted users won't see it, but screen readers can still read it.


Mistake 5. Missing or meaningless alt text

alt is the text that stands in for an image when it can't be seen: by screen readers, when the image fails to load, and by search engines.

SituationWhat to write
Image that conveys contentDescribe what it shows: alt="Bar chart showing Q3 revenue up 20%"
Decorative image (background patterns, flourishes)Empty: alt="" (screen readers skip it)
Icon inside a link or buttonDescribe the action: alt="Go to cart"
<!-- ❌ With no alt, some screen readers read out the file name ("img underscore zero one two three dot png") -->
<img src="img_0123.png" />

<!-- ❌ Meaningless alt -->
<img src="chart.png" alt="image" />

<!-- ✅ -->
<img src="chart.png" alt="Bar chart showing Q3 revenue up 20%" />
<img src="divider.svg" alt="" />

Leaving out the alt attribute and setting it to empty (alt="") are different things. Empty means "this is decoration, skip it." Missing means "someone forgot."


Mistake 6. Building lists out of divs

Menus, tag lists, search results: when the same kind of thing repeats, it's a list.

<!-- ❌ -->
<div class="menu">
  <div><a href="/">Home</a></div>
  <div><a href="/blog">Blog</a></div>
  <div><a href="/about">About</a></div>
</div>

<!-- ✅ -->
<nav aria-label="Main menu">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/blog">Blog</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

With list tags, screen readers announce "list, 3 items" up front, so users know how long the list is before they start. If order matters (install steps, rankings), use ol.


Mistake 7. Building the page skeleton entirely from divs

Tags like header, nav, main, and footer are called landmarks. Screen reader users use them to jump around: "skip to main content," "go to navigation." Think of them as the numbered exits of a subway station.

If you're writing thisWrite this insteadMeaning
<div class="header"><header>Site logo, top area
<div class="nav"><nav>Main menu, groups of navigation links
<div class="main"><main>The page's core content (one per page)
<div class="post"><article>Self-contained content (a post, a comment, a product card)
<div class="section"><section>A thematic group with its own heading
<div class="sidebar"><aside>Related but secondary content
<div class="footer"><footer>Copyright, contact info, and other bottom matter
<body>
  <header>
    <a href="/">Logo</a>
    <nav aria-label="Main menu">...</nav>
  </header>

  <main>
    <article>
      <h1>Post title</h1>
      <section>
        <h2>First subtopic</h2>
        <p>...</p>
      </section>
    </article>
    <aside>Related posts</aside>
  </main>

  <footer>© 2026</footer>
</body>

Going the other way and turning every div into a section is also a mistake. Use section only for a meaningful unit with a heading. If it's just a wrapper for styling, div is correct. div isn't a bad tag; it means "a box with no special meaning."


ARIA is a last resort

Once you start caring about accessibility, you'll run into ARIA attributes like role and aria-label. They're powerful, but they come with a first rule:

If a native HTML element does the job, use that element instead of ARIA.

<button> beats <div role="button" tabindex="0">, and <nav> beats <div role="navigation">. ARIA only says "this is a button"; it doesn't add any keyboard behavior. Wrong ARIA is more confusing than no ARIA at all.


Is my page OK? A 3-minute check

HowWhat to check
Put the mouse down and use only TabCan you reach every button and link? Is the focus outline visible?
Dev tools → Lighthouse → Accessibility auditMissing alt, missing label, color contrast issues
Dev tools → Elements → Accessibility tabWhat role and name each element is announced with
List just the headings (run the code below in the console)Are any heading levels skipped?
// Paste into the console to print the page's heading structure, indented
document.querySelectorAll("h1,h2,h3,h4,h5,h6").forEach((h) =>
  console.log(" ".repeat((h.tagName[1] - 1) * 2) + h.tagName + " " + h.textContent.trim())
);

Summary: why this is worth knowing

MistakeFix
Clickable divbutton
href="#" links that run actionsNavigation is a, actions are button
Headings picked by font sizeFollow the structure h1→h2→h3, size with CSS
Inputs with only a placeholderConnect a label
Missing or meaningless altDescribe the content, or alt="" for decoration
Lists made of divsul/ol + li
Page skeleton made only of divsheader, nav, main, article, footer

Semantic HTML isn't a separate "accessibility task" you set time aside for. It's the habit of thinking once more when you pick a tag. That one habit lets keyboard and screen reader users actually use your site, helps search engines understand your content, and lets the teammate who reads your code later see its structure at a glance. Next time you're about to type div, ask yourself first: "Is this really a box with no meaning?"