HTML formatting best practices
Conventions that pay for themselves: the structural ones that affect real users, and the stylistic ones that stop code review turning into an argument about whitespace.
Structure
Declare the doctype, always
Without <!DOCTYPE html> on the first line, browsers switch to quirks mode and apply
twenty-year-old layout rules. The box model changes. Your CSS stops behaving. It is one line and there is no
situation where omitting it helps.
Set lang on the html element
<html lang="en"> tells screen readers which pronunciation rules to use and helps
browsers offer translation correctly. A page in the wrong voice is close to unusable for someone relying on
audio.
Use elements for what they mean
A <nav> rather than <div class="nav">. A <button>
rather than a clickable <div>. Semantic elements come with keyboard behaviour, screen
reader announcements and browser defaults that you would otherwise have to rebuild — usually incompletely.
Keep heading levels in order
One <h1> per page, then <h2> for sections, then
<h3> beneath those. Do not skip levels to get a particular size — that is what CSS is for.
Screen reader users navigate by heading, and a broken hierarchy is a broken table of contents.
Writing the markup
Lower-case tags and attribute names
HTML is case-insensitive, so <DIV> works. Mixed case in one file is still visual noise
that costs a fraction of a second every time someone scans it. Lower-case is universal convention.
Double-quote every attribute value
HTML5 permits unquoted values, and permits single quotes. Neither is worth the inconsistency. Unquoted values also break silently the moment a value contains a space.
<img src=photo.jpg alt=A photo of a cat>
<img src="photo.jpg" alt="A photo of a cat">
In the first example the alt attribute is the single word A, and
photo, of, a and cat have become four separate boolean
attributes. The browser will not tell you.
Do not close void elements
<br>, not <br /> or <br></br>. The XHTML
slash is harmless but meaningless in HTML5, and the closing tag is invalid. The same applies to
<img>, <hr>, <input>, <meta> and
<link>.
Write closing tags even where they are optional
HTML lets you omit </li>, </p> and </td>. Do not.
Explicit closures make nesting visible to the next reader and remove a whole class of ambiguity when
something goes wrong.
Keeping it lean
Drop redundant attributes
type="text/javascript" on a script tag and type="text/css" on a stylesheet link
are both defaults in HTML5. Boolean attributes need no value: required, not
required="required".
Keep styling in the stylesheet
Inline style attributes are hard to override, impossible to reuse, invisible to your design
system and a problem for Content Security Policy. The exceptions are genuinely dynamic values and HTML email,
where inline styles are the only thing that reliably works.
Delete wrappers that do nothing
A <div> with no class, no id and one child is usually a leftover from a layout approach
you have since abandoned. Formatting the file makes these obvious — they show up as a step in the indent with
nothing to justify it.
Give every image an alt attribute
Descriptive text for images that carry meaning, and alt="" for purely decorative ones. Empty
is correct and deliberate; missing is a failure. Screen readers announce the filename when
alt is absent, which is worse than silence.
Formatting habits
Automate it
Format on save in your editor, or a pre-commit hook, or a CI check. Any of the three. What matters is that no human ever has to decide, because humans forget and then argue about it in code review.
Commit an .editorconfig
It is read by nearly every editor, often without a plugin, and it settles indent style, line endings and trailing whitespace for the whole project in eight lines.
Keep whitespace changes in their own commit
Reformatting a file touches every line. Mixed into a functional change, it makes the diff unreviewable. Separate commit, clear message, move on.
Validate before you ship
Structural checks catch unclosed tags and duplicate IDs in seconds. Run the validator on templates after a big edit, and the W3C service before a launch.
A reference skeleton
Everything above, applied to a page head.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Descriptive, unique page title</title>
<meta name="description" content="One sentence a search engine can show.">
<link rel="canonical" href="https://example.com/page/">
<link rel="stylesheet" href="/assets/css/site.css">
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header>…</header>
<main id="main">…</main>
<footer>…</footer>
<script src="/assets/js/site.js" defer></script>
</body>
</html>
charset comes first because it must appear within the first 1024 bytes. The script carries
defer so it does not block rendering. The skip link is the first focusable element on the page,
which is what makes it useful.
Questions about this
Is there an official HTML style guide?
There is no single official one. Google's HTML/CSS Style Guide is the most widely cited and is a reasonable default. The WHATWG specification defines what is valid, not what is stylish. Whatever you adopt, write it down and make a tool enforce it.
Do these rules matter for a small personal site?
The structural ones do — doctype, lang, alt text and heading order affect real people using screen readers regardless of how many visitors you have. The formatting conventions matter mostly in proportion to how many people touch the file.
Should I use XHTML-style self-closing tags?
No, not in an HTML5 document. <br /> is parsed identically to <br>, so it is harmless, but it signals a document type you are not using. The exception is inline SVG, where XML rules apply and self-closing tags are required.
How strict should I be about semantic elements?
Strict where it changes behaviour, relaxed where it does not. <button> versus a clickable <div> is a real accessibility difference and worth insisting on. <section> versus <div> for a generic wrapper rarely changes anything for anyone.
Try it on your own file
The formatter, minifier and validator are all on the front page.