Guide · 9 min read · Updated

20 HTML best practices for clean, valid code

Illustration of a tidy desk with neatly indented code on a monitor and a checklist

Here is a bug that can eat a whole afternoon. A page looks fine in your browser, then a colleague opens it on a phone and the footer sits halfway up the screen. The cause turns out to be one div that was never closed, which your browser patched one way and theirs patched another. Browsers will render almost anything by guessing what you meant, so broken HTML "works" right up until it does not.

The 20 rules below are the ones we apply to every file on this site. They are grouped into eight sections, each with an example, and every rule prevents a specific, real problem. None of them are about taste. Paste the examples into the playground if you want to see them break.

Start every page with the same five lines

  1. Put <!DOCTYPE html> on the very first line. Leave it out and the browser drops into quirks mode (opens in a new tab), imitating the inconsistent behaviour of browsers from around 1999.
  2. Set lang on the <html> element. Screen readers pick their pronunciation from it, and browsers use it for translation and hyphenation.
  3. Declare UTF-8 early. <meta charset="utf-8"> has to appear within the first 1024 bytes of the file, so make it the first thing in <head>. Save the file as UTF-8 as well, or an é turns into two characters of nonsense.
  4. Add the viewport tag so phones show the page at their own width.
  5. Give each page its own descriptive <title>.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Opening Hours - Corner Bakery</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  ...
</body>
</html>

You can build this skeleton piece by piece in The Page Skeleton and Title and Charset.

Write syntax that looks the same everywhere

  1. Write element and attribute names in lowercase. HTML does not care about case, but every style guide does, lowercase reads faster, and XML tools require it.
  2. Put attribute values in double quotes. An unquoted value breaks the moment it contains a space:
    <!-- Breaks: the browser sees class="card" and an attribute named "featured" -->
    <div class=card featured>
    <!-- Correct -->
    <div class="card featured">
  3. Close elements, and close them in reverse order. A few end tags, </p> and </li> among them, are technically optional; write them anyway and you will never wonder where a paragraph stops. Void elements such as <img>, <br>, <input> and <meta> have no end tag. A trailing slash (<br />) is allowed and does nothing, so choose one style and stick to it.
    <!-- Wrong nesting -->
    <p><strong>Fresh bread</p></strong>
    <!-- Correct -->
    <p><strong>Fresh bread</strong></p>
  4. Indent every nested level by the same amount (two spaces is the usual choice) and never mix tabs with spaces. The browser ignores indentation. You will not, because a misplaced tag shows up as a crooked line. If you inherit a file indented by five different people, a formatter such as Pretty HTML (opens in a new tab) re-indents it in one click.

The basics are in Tags and Elements.

Pick elements for what the content is

  1. Try a semantic element before a <div>. <header>, <nav>, <main>, <article>, <section>, <aside> and <footer> cover most page regions. A <div> is for grouping that means nothing. See Header, Nav, Main, Footer and the semantic HTML guide.
  2. Keep headings in order: one <h1>, then <h2>, <h3> and so on, with no skipped levels. The level reflects importance. Font size is CSS's business.
  3. Links go somewhere and buttons do something. Navigate with <a href>, act with <button>, and always give a button a type, because inside a form a button with no type submits the form.
    <a href="/menu/">See the menu</a>
    <button type="button">Show allergens</button>
  4. Use lists, tables and quotes for what they are. A navigation menu is a list of links. A bus timetable is a table with <th> header cells. Tables do not do page layout, and a stack of <br> tags or empty paragraphs is not spacing; CSS margins are.

Images and form fields

  1. Every image gets an alt attribute. Describe what the image tells the reader, or write alt="" if it is pure decoration. See Good Alt Text.
  2. Set width and height on images. The browser then reserves the right amount of space before the file arrives and the text below stays put. CSS can still make the image responsive.
    <img src="img/croissants.jpg" alt="Tray of golden croissants" width="800" height="533">
  3. Label every field and give it the right type. Tie a <label> to each input with for and id. Specific types such as email, tel, number and date bring up the right keyboard on a phone, and the browser checks the format at no cost to you.
    <label for="phone">Phone number</label>
    <input type="tel" id="phone" name="phone" autocomplete="tel">

The Labels and Input Types lessons go into both.

Keep CSS and JavaScript out of the markup

  1. Avoid inline styles and inline event handlers. HTML holds the content, CSS the appearance and JavaScript the behaviour, and when all three share one line, every change touches all of them. Text pasted from a word processor is usually stuffed with inline styles; HTML Cleaner (opens in a new tab) strips those out, along with stray classes and empty tags.
<!-- Avoid -->
<button style="color:red" onclick="order()">Order</button>
<!-- Prefer -->
<button type="button" class="order-btn" id="order">Order</button>
<script src="js/app.js" defer></script>
// js/app.js
document.getElementById("order").addEventListener("click", order);

Inline handlers such as onclick cause trouble of their own. They run in an odd scope, they are awkward to remove, and a strict Content Security Policy (a common security header) blocks them outright. Styles belong in an external stylesheet linked from the <head>. Scripts load with defer so they do not hold up rendering. Both are covered in Linking CSS and JavaScript and External Stylesheets.

Name files so servers cannot misread them

  1. File names are lowercase, with hyphens and no spaces. Most web servers are case-sensitive, which makes About.html and about.html two different files. A space becomes %20 in the URL and invites typos.
AvoidUse
My Page.HTMLmy-page.html
IMG_4031.JPGsourdough-loaf.jpg
contact_us_FINAL2.htmlcontact.html

The home page of each folder is index.html, which the server shows when someone visits the folder URL. Assets go in obvious folders such as css/, js/ and img/, linked with relative paths as described in Relative and Absolute Paths. Separate words with a hyphen in anything that ends up in a URL. Google reads a hyphen as a word break, according to its URL structure guidelines (opens in a new tab), so sourdough-loaf is two words to it and sourdough_loaf may be one.

Comments that explain, and no obsolete tags

  1. Use comments to explain things, never to store old code, and leave obsolete markup out. A one-line comment can mark where a long block ends or why something odd is there. Every visitor downloads your comments and anyone can read them with View Source, so passwords and private notes never go in them.
<!-- Product grid: items are generated by the shop system -->
<section class="products">
  ...
</section>

The HTML Living Standard marks <font>, <center>, <marquee>, bgcolor and align as obsolete, among others. Browsers still render many of them so that 1998 pages keep working, but CSS does the same job properly. When you clean up an old page, run it through an HTML tidy tool (opens in a new tab) first; once the markup is neatly formatted, these leftovers stand out. The syntax is in the Comments and Entities lesson.

Run every page through the W3C validator

  1. Check each page with the W3C Nu HTML Checker, the free online W3C validator (opens in a new tab). Paste code, upload a file or give it a URL. It reports unclosed elements, duplicate id values, attributes on the wrong element and missing required attributes.

Fix the first error before anything else. One missing end tag can set off fifteen follow-up errors, and they all vanish once it is fixed. Errors are must-fix. Warnings are advice, so read them and decide. And validate while you build instead of once at the end; many editors can run a linter such as HTMLHint as you type.

A page can pass the validator and still be inaccessible or badly designed. What validation does is remove a whole category of bugs that are miserable to track down later. Pair it with a quick keyboard test and an accessibility checker, and keep the HTML cheat sheet nearby for element names and attributes. If you are unsure how an element should be used, look it up in the reference, for example img or label.

Frequently asked questions

Do I need to close all HTML tags?

Not all of them. Void elements such as <img>, <br>, <input> and <meta> never have an end tag, and for a few others, <p> and <li> included, the standard makes the end tag optional. We still write every end tag explicitly. The structure is obvious at a glance and the parser has nothing to guess.

Should HTML tags be uppercase or lowercase?

Lowercase. HTML is case-insensitive, so <P> and <p> behave the same, but the standard, style guides and almost all modern code use lowercase. It reads more easily and stays valid if the markup is ever processed as XML. Keep file names lowercase too.

How do I validate my HTML code?

Go to the free W3C Nu HTML Checker at validator.w3.org/nu. Paste your code, upload the file or enter the page URL, and read the list of errors and warnings. Start with the first error, because one mistake can trigger several follow-up errors, and keep validating until none are left.

Is a trailing slash needed on void elements like br and img?

No. <br> and <br /> mean exactly the same in HTML; the slash is allowed and ignored. XHTML required it, which is why it still shows up in older code. Pick one style per project. Do not put a slash on a non-void element such as <div />, though, since it does not close it.

Why should I avoid inline onclick attributes?

They mix behaviour into the markup, so the same code is hard to maintain or reuse. They also run in an unusual scope, are difficult to remove, and are blocked by a strict Content Security Policy (opens in a new tab). Give the element an id or class instead and attach the handler from an external script with addEventListener.

Practise it in the course

Try it in the playground

More guides