HTML Best Practices: Writing Clean, Semantic, Accessible Markup
HTML is deceptively easy to write badly. A page can look correct in a browser while being a maintenance nightmare, inaccessible to screen readers, slow to parse, and fragile to change. These practices help you write HTML that holds up over time.
Format messy HTML instantly: Our HTML Formatter indents and beautifies HTML code โ paste in minified or messy markup and get clean, readable output.
Open HTML Formatter โTable of Contents
Use Semantic Elements
Semantic HTML uses elements whose names describe their purpose: <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>. Screen readers use these to build document outlines. Search engines use them to understand content structure. Use them correctly and your HTML is self-documenting.
<!-- Non-semantic: everything is divs -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="main">...</div>
<!-- Semantic: structure is explicit -->
<header>...</header>
<nav aria-label="Main navigation">...</nav>
<main>...</main>
One H1 Per Page
The <h1> element should appear once and describe the page's main topic. Headings should be hierarchical โ don't skip from H1 to H4. Screen readers allow users to navigate by heading level, so a logical heading structure is a core accessibility requirement.
Always Include Alt Text on Images
Every <img> needs an alt attribute. If the image is informative, describe what it shows: alt="Bar chart showing 40% revenue growth in Q3 2025". If the image is purely decorative, use an empty alt: alt="" โ this tells screen readers to skip it. Never omit the attribute entirely.
Use <button> for Actions, <a> for Navigation
The distinction matters for keyboard accessibility and screen reader announcements. <button> triggers an action (submit, toggle, open modal). <a href="..."> navigates to a URL. Never use a <div onclick="..."> or a <a href="#"> (no real destination) for either purpose.
Form Accessibility
<!-- Every input needs an associated label -->
<label for="email">Email address</label>
<input type="email" id="email" name="email"
autocomplete="email" required
aria-describedby="email-hint">
<span id="email-hint">We'll never share your email.</span>
<!-- Don't use placeholder as a label substitute -->
<!-- Placeholder disappears on input and has poor contrast -->
Boolean Attributes
Boolean HTML attributes don't need a value โ their presence alone means true. required, disabled, checked, readonly, multiple, autofocus are all boolean. Don't write required="true" or disabled="disabled".
Document Structure Checklist
<!DOCTYPE html>as the very first line<html lang="en">with a correct language attribute<meta charset="UTF-8">as the first tag in<head><meta name="viewport" content="width=device-width, initial-scale=1">- A descriptive
<title>(not just "Home" or the company name) - A
<meta name="description">for search engines - All CSS in
<head>, all<script>tags before</body>(or usedefer)
Loading Performance
Add loading="lazy" to images below the fold. Add width and height attributes to images to prevent layout shift during load. Use <link rel="preconnect"> for critical third-party origins. Add defer or async to non-critical script tags.
Performance: Core Web Vitals in 2026
Google's Core Web Vitals are the primary performance metrics that affect search ranking. Understanding them helps you write HTML that performs well:
- LCP (Largest Contentful Paint) โ Time until the largest visible element (hero image, headline) loads. Target: under 2.5 seconds. Improve with
rel="preload"for critical images, proper image sizing, and avoiding render-blocking resources. - INP (Interaction to Next Paint) โ Measures responsiveness to all user interactions (clicks, taps, keyboard). Replaced FID (First Input Delay) as a Core Web Vital in March 2024. Target: under 200ms. Improve by keeping JavaScript off the main thread and breaking up long tasks.
- CLS (Cumulative Layout Shift) โ Measures visual stability โ how much content jumps around while loading. Target: under 0.1. Fix by always specifying
widthandheightattributes on images and reserving space for ads/embeds before they load.
<!-- Always specify dimensions to prevent layout shift (CLS) -->
<img src="hero.jpg" alt="Product hero" width="800" height="600">
<!-- Preload LCP image to improve LCP score -->
<link rel="preload" as="image" href="hero.jpg" fetchpriority="high">
<!-- Lazy-load below-fold images (don't lazy-load the LCP image!) -->
<img src="product.jpg" alt="Product" loading="lazy" width="400" height="300">
