Clean HTML: Structure, Naming and Maintainability
Markup you can still read in six months. Consistent conventions, meaningful names, shallow nesting and a house style that survives more than one developer.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Concept
Markup is read far more often than it is written. Most of the reading is done by someone who did not write it - including you, six months later, with no memory of why anything is the way it is.
Clean HTML is not an aesthetic preference. It is the difference between a change taking ten minutes and taking an afternoon.
Consistency first
Almost every convention below is arguable. Being consistent about whichever one you pick matters more than the choice itself.
<!-- pick one and hold to it -->
<img src="a.jpg" alt="A map"> <!-- HTML style -->
<img src="a.jpg" alt="A map" /> <!-- XHTML style, ignored by the parser -->
<!-- one quote style -->
class="card"
class='card'
<!-- one attribute order -->
<a class="btn" href="/x" data-id="1">
<a href="/x" class="btn" data-id="1">A formatter running on save settles most of this without anyone thinking about it, which is why it is worth setting up on day one.
Indentation
<!-- structure invisible -->
<article class="card"><h3>Design</h3><p>Three years</p><a href="/x">More</a></article>
<!-- structure visible from ten feet away -->
<article class="card">
<h3>Design</h3>
<p>Three years</p>
<a href="/x">More</a>
</article>One level of indentation per level of nesting. Two spaces is the common default and four is equally fine; what matters is that the file uses one of them throughout.
Break long attribute lists across lines:
<img src="/images/studio-800.jpg"
srcset="/images/studio-400.jpg 400w, /images/studio-800.jpg 800w"
sizes="(max-width: 50rem) 100vw, 50vw"
alt="Students working at drawing boards"
width="1600" height="900"
loading="lazy">Name by meaning
<!-- names the appearance: a lie waiting to happen -->
<p class="red-text small-font left-align">Out of stock</p>
<!-- names the meaning: still true after a redesign -->
<p class="stock-status stock-status--out">Out of stock</p>The day the design changes to orange, class="red-text" becomes a permanent piece of misinformation in the codebase.
A naming convention
<article class="course-card course-card--featured">
<h3 class="course-card__title">Design</h3>
<p class="course-card__summary">Three years, studio based.</p>
<a class="course-card__link" href="/courses/design">Read more</a>
</article>Block, element, modifier. Verbose, and it makes every selector a flat single class with identical specificity, which removes nesting battles entirely. Any convention works; having one is the point.
State classes
<div class="menu is-open">
<button class="btn is-loading">
<li class="tab is-active">An is- prefix marks classes that scripts toggle, so it is obvious which are dynamic. A data- attribute is an alternative when the states are mutually exclusive.
Keep nesting shallow
<!-- seven levels before any content -->
<div class="wrapper">
<div class="container">
<div class="row">
<div class="col">
<div class="card">
<div class="card-inner">
<div class="card-body">
<h3>Design</h3>
<!-- two -->
<div class="grid">
<article class="card">
<h3>Design</h3>CSS grid and flexbox removed most of the reason for wrapper chains. Every level you delete makes the file shorter, the selectors simpler and the structure clearer.
Comment the why
<!-- pointless: the element already says this -->
<!-- header -->
<header>...</header>
<!-- useful: records something the markup cannot express -->
<!-- Column order matters: the middle column is styled as the highlighted
plan in pricing.css. Do not reorder without updating that file. -->
<table class="pricing">...</table>
<!-- useful in a deeply nested file -->
</div><!-- /site-header -->One idea per file or partial
Split templates by responsibility rather than by page:
templates/
layout.html doctype, head, header, footer
partials/
site-header.html
site-footer.html
course-card.html
breadcrumb.html
pages/
courses.html
course-detail.htmlA component defined once and included everywhere means one place to fix an accessibility problem, not fourteen.
Write in reading order
<!-- main content first, whatever the visual layout -->
<main>...</main>
<aside>...</aside>Source order is keyboard order, screen reader order and mobile stacking order. Write the markup in the order it should be read and move it visually with CSS.
A house style worth adopting
- Lower case element and attribute names.
- Double quoted attribute values, always.
- Two space indentation, one level per nesting level.
- Attribute order: identity, then behaviour, then data and ARIA.
- Write closing tags even where they are optional.
- Void elements without a trailing slash.
- Class names by meaning, in one convention.
- One
h1, no skipped heading levels. - Semantic elements before
div. - Comment constraints, not descriptions.
Write it down in the repository. A convention nobody can find is not a convention.
Signs a file needs attention
- More than about four wrapper levels with no content between them.
- Class names that match element names:
class="header",class="list-item". - Inline
styleattributes scattered through the markup. - The same block copied in three places.
- Comments describing what an element obviously is.
- A file over about five hundred lines with no partials.
- Any
divwith a click handler.
Important rules
- Consistency beats any individual convention.
- Class names should survive a redesign.
- Source order is reading order.
- A formatter enforces style; a linter enforces correctness.
- Every deleted wrapper is a small permanent gain.
Common mistakes
- A different style in every file.
- Appearance based class names.
- Wrapper chains inherited from an older layout approach.
- Copying a block rather than extracting a partial.
- Comments that restate the markup.
- Conventions agreed verbally and never written down.
Best practices
- Set up a formatter and a linter before writing the second file.
- Commit the configuration so everyone shares it.
- Name by meaning, in one convention, written down.
- Extract a partial the second time you copy something.
- Delete wrappers that only exist for an older layout method.
- Review markup in pull requests the way you review code.
Practice
- Take a file you wrote a while ago and count the wrapper levels before the first content element.
- List every class name in a project and mark those that name an appearance.
- Find a block duplicated three times and extract it into a partial.
- Write a one page house style for a project and apply it to two files.