~/TechPurAI
~/tutorials/html-from-scratch/common-html-mistakes-that-hurt-seo-and-accessibility
intermediate·part 20 of 22·8 min read

Common HTML mistakes that quietly hurt SEO and accessibility

Updated Aug 31, 2026HTML

Every part in this series flagged one mistake in its own Callout, building Bright Leaf Coffee's real site. This part collects all 15 in one place, each with the markup that causes it and the markup that fixes it — a reference to check a real page against before publishing it.

Mistake 1: choosing headings by visual size, not real hierarchy (part 3)

html
<p style="font-size: 24px; font-weight: bold;">Our Coffee Origins</p>
html
<h2>Our Coffee Origins</h2>

Cost: a styled paragraph looks identical to a heading but isn't one — both search engines and screen readers rely on real heading elements to understand a page's outline, and a bolded, oversized <p> is invisible to both.

Mistake 2: fake lists built from paragraphs and manual bullets (part 4)

html
<p>• Ethiopian Yirgacheffe<br>• Colombian Supremo<br>• Sumatra Mandheling</p>
html
<ul>
  <li>Ethiopian Yirgacheffe</li>
  <li>Colombian Supremo</li>
  <li>Sumatra Mandheling</li>
</ul>

Cost: a screen reader announces a real <ul> as "list, 3 items" and lets a visitor jump between items — a paragraph with manual bullet characters and <br> tags announces as one unbroken sentence, with no item count or navigation.

html
<a href="/subscriptions" target="_blank">Subscriptions</a>
<a href="https://roaster-partner.example.com" target="_blank">Our Roaster</a>
html
<a href="/subscriptions">Subscriptions</a>
<a href="https://roaster-partner.example.com" target="_blank" rel="noopener noreferrer">Our Roaster</a>

Cost: an internal link forced into a new tab produces unwanted, accumulating tabs on your own site. An external link opened with target="_blank" and no rel="noopener" gives the new page's JavaScript a real reference back to window.opener — a documented security gap, not a theoretical one.

Mistake 4: alt text that's missing, or that's just "image" (part 6)

html
<img src="coffee-bag.jpg" alt="image">
html
<img src="coffee-bag.jpg" alt="1lb bag of Bright Leaf Ethiopian Yirgacheffe whole bean coffee">

Cost: this is the single most common, most avoidable gap on this list — alt="image" gives zero real information to a screen reader user or to an image search crawler, exactly as if the attribute were missing entirely.

Mistake 5: <section> used as a generic div replacement (part 7)

html
<section class="button-group">
  <button>Add to cart</button>
  <button>Save for later</button>
</section>
html
<div class="button-group">
  <button>Add to cart</button>
  <button>Save for later</button>
</div>

Cost: <section> is meant for a thematic grouping that would make sense with its own heading — a button group has no such theme, so reaching for <section> here is no more semantically meaningful than a <div> would have been, while looking like it should be.

Mistake 6: a real data table with no <th> or scope (part 9)

html
<table>
  <tr><td>Origin</td><td>Roast</td><td>Price</td></tr>
  <tr><td>Ethiopia</td><td>Light</td><td>$16</td></tr>
</table>
html
<table>
  <tr><th scope="col">Origin</th><th scope="col">Roast</th><th scope="col">Price</th></tr>
  <tr><th scope="row">Ethiopia</th><td>Light</td><td>$16</td></tr>
</table>

Cost: sighted visitors visually associate "Ethiopia" with "Origin" from position alone. A screen reader has no visual layout to lean on — scope="col" and scope="row" are what let it announce "Origin: Ethiopia" instead of reading three disconnected cells.

Mistake 7: placeholder text used as a field's only label (part 10)

html
<input type="email" placeholder="Email address">
html
<label for="email">Email address</label>
<input type="email" id="email" placeholder="you@example.com">

Cost: placeholder text vanishes the instant a visitor starts typing, and isn't reliably announced by every screen reader beforehand either — a real <label> stays visible and accessible for the entire time the field is in view.

Mistake 8: color as the only error indicator on a form (part 11)

html
<input type="email" style="border-color: red;">
html
<input type="email" aria-invalid="true" aria-describedby="email-error">
<span id="email-error">Enter a valid email address</span>

Cost: a red border communicates nothing to a screen reader, and fails for visitors with color blindness too — aria-invalid and a real, associated error message communicate the same problem in a form every visitor can perceive.

Mistake 9: video or audio with no real captions or transcript (part 12)

html
<video src="brewing-guide.mp4" controls></video>
html
<video src="brewing-guide.mp4" controls>
  <track kind="captions" src="brewing-guide.vtt" srclang="en" label="English">
</video>

Cost: uncaptioned video is inaccessible to deaf and hard-of-hearing visitors, and it's also invisible to search engines, which can't watch or listen to a video directly — a <track> element is the one piece of markup that fixes both problems at once.

Mistake 10: an iframe with no title attribute (part 13)

html
<iframe src="https://maps.example.com/embed?..."></iframe>
html
<iframe src="https://maps.example.com/embed?..." title="Map showing Bright Leaf Coffee's roastery location"></iframe>

Cost: a screen reader announces an embedded frame with zero indication of what it actually contains — a real title is the only thing that turns "frame" into "map showing our roastery location."

Mistake 11: the same meta description copied across every page (part 14)

html
<!-- every single page on the site -->
<meta name="description" content="Bright Leaf Coffee — quality coffee delivered to your door.">
html
<!-- /subscriptions specifically -->
<meta name="description" content="Compare Bright Leaf Coffee's 3 subscription plans — weekly, biweekly, or monthly delivery of freshly roasted single-origin beans.">

Cost: a generic description undersells what each individual page actually offers in a search result — a real, avoidable click-through-rate cost, since the snippet is often the deciding factor between two similar results.

Mistake 12: shipping with no Open Graph tags at all

html
<!-- nothing in <head> describing how this page should look when shared -->
html
<meta property="og:title" content="Bright Leaf Coffee Subscriptions">
<meta property="og:description" content="Weekly, biweekly, or monthly coffee delivery — pause or cancel anytime.">
<meta property="og:image" content="https://brightleafcoffee.example.com/og/subscriptions.jpg">

Cost: with no Open Graph tags, a platform falls back to guessing an image and snippet from the page itself — often producing a broken or genuinely embarrassing shared-link preview at the exact moment someone is trying to share the page.

Mistake 13: JSON-LD that claims something the visible page doesn't (part 16)

html
<script type="application/ld+json">
{ "@type": "Product", "aggregateRating": { "ratingValue": "4.8", "reviewCount": "312" } }
</script>
<!-- but the visible page shows no reviews or ratings anywhere -->
html
<!-- omit aggregateRating entirely unless the page actually displays real,
     verifiable reviews a visitor can see -->
<script type="application/ld+json">
{ "@type": "Product", "name": "Ethiopian Yirgacheffe, 1lb" }
</script>

Cost: structured data describing content the page doesn't actually show is a real, documented guideline violation search engines actively check for — and separately, a small JSON syntax error in a block like this fails completely silently, with the page still rendering normally while the structured data is simply ignored.

Mistake 14: removing the focus outline with no visible replacement (part 17)

css
:focus {
  outline: none;
}
css
:focus-visible {
  outline: 2px solid #1a5c38;
  outline-offset: 2px;
}

Cost: removing the outline with nothing in its place makes keyboard navigation genuinely unusable — there's no way to tell which element currently has focus. :focus-visible (rather than :focus) shows the outline for keyboard users specifically, without adding it on every mouse click.

Mistake 15: lazy-loading the one image visible immediately on load (part 19)

html
<img src="hero-coffee-bag.jpg" loading="lazy" alt="Bright Leaf Coffee's signature blend">
html
<img src="hero-coffee-bag.jpg" fetchpriority="high" alt="Bright Leaf Coffee's signature blend">

Cost: loading="lazy" tells the browser to delay fetching an image until it's about to scroll into view — applied to an image that's already visible the moment the page loads, it actively delays Largest Contentful Paint instead of improving it. fetchpriority="high" does the opposite: it tells the browser this specific image matters for the initial render.

The thread connecting all fifteen

Every mistake above shares the same root cause as the ones catalogued in the CSS series' own roundup: something that looks visually correct while being structurally or semantically wrong underneath — invisible to a quick glance, real to a screen reader, a search engine, or a social platform's link preview.

Common mistake

Treating this list as a one-time reference read once at the end of the series, rather than a genuine pre-launch checklist revisited for every new page. A pattern learned once in part 6 (real alt text) is easy to forget by the time a later page gets built — worth an explicit check against this list before any new page ships.

FAQ

Which of these has the biggest SEO impact specifically? Missing or generic alt text (Mistake 4) and duplicate meta descriptions (Mistake 11) are the two most directly tied to search visibility — both affect how a page is understood and how it's presented in results, independent of anything a screen reader does.

Do I need scope on every table, even small ones? For any table with a header row or header column a screen reader would otherwise have to guess at, yes. A purely layout table (rare, and generally worth avoiding in favor of CSS Grid or Flexbox) doesn't need it because it isn't presenting real tabular data.

Is :focus-visible supported everywhere? Yes, in every current browser. For older browser support, a :focus fallback that only shows for keyboard users via a small script is the pre-:focus-visible pattern — not necessary for a new site today.

Can I check for these automatically instead of manually? Browser DevTools' Lighthouse and axe DevTools (a free browser extension) catch most of these automatically — missing alt text, missing form labels, low-contrast focus indicators. They won't catch Mistake 13 (JSON-LD claiming something untrue) since that requires comparing structured data against what the page actually displays, which needs a human read.

Next: organizing a real multi-page site — file structure, relative linking across multiple pages, and building Bright Leaf Coffee's Subscriptions and Contact pages.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous19. HTML performance basics: lazy loading, preload, and script loadingnext →21. Organizing a real multi-page site: file structure and the Subscriptions page