A complete, minimal document

This is the smallest page you should write for real use. Every line has a job, explained in the rest of this page:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Meter readings — Kestrel Energy</title>
  </head>
  <body>
    <h1>Meter readings</h1>
    <p>Enter your electricity and gas readings before the 5th of each month.</p>
  </body>
</html>
  • <!doctype html> tells the browser to use standards mode.
  • <html lang="en"> is the root element; the lang attribute says the page is in English (see Metadata, Sharing and Search Basics).
  • The <head> holds the character encoding, the viewport setting and the title shown in the browser tab.
  • The <body> holds everything on screen.

The indentation is only for people. Browsers ignore it, but it shows at a glance which element sits inside which.

The doctype

The doctype (document type declaration) is the line <!doctype html> at the very top of an HTML document. It is not an element and is not shown. The HTML standard calls it "a required preamble" that exists "for legacy reasons": its only job today is to switch the browser into standards mode.

Without it, browsers fall back to quirks mode, in which they imitate the layout bugs of late-1990s browsers so that very old pages keep working. A modern page in quirks mode can show odd sizes and spacing, for example in tables and with percentage heights. The doctype is case-insensitive: <!DOCTYPE html> and <!doctype html> are the same.

Older pages carry long doctypes such as <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" …>. The Strict ones, like this one, still trigger standards mode; many Transitional and older doctypes trigger quirks or "almost standards" mode instead, so replace them with <!doctype html>.

Elements, tags and content

An HTML element is one building block of a page: a heading, a paragraph, a link, an image. In the source it is usually written as a start tag, some content and an end tag:

<p class="note">Your reading was saved.</p>
  • <p class="note"> is the start tag: the element name p plus an attribute.
  • Your reading was saved. is the content.
  • </p> is the end tag: the same name with a slash.

People often say "tag" when they mean the whole element. Strictly, the tags are only the parts in angle brackets; the element is the thing they create in the DOM. Element names are case-insensitive (<P> works), but lowercase is the universal convention.

Nesting: boxes inside boxes

Content can include other elements. The rule is the same as for brackets in math: what opens last closes first.

<p>Call us on <strong><a href="#">0800 123 4567</a></strong> if you smell gas.</p>

Here a sits inside strong, which sits inside p. The element that contains another is its parent; the contained one is its child. The whole page forms a tree in this way:

The element tree of the minimal document: html contains head and body; head contains two meta elements and title; body contains h1 and p. The title, h1 and p contain text. html head body meta meta title h1 p text text text
The minimal document as a tree. The browser builds this tree, the DOM, from the tags.

Tags must not overlap. <b><i>…</b></i> is wrong because i was opened last but is closed last too. The browser will repair it (see Browsers forgive broken HTML), but the resulting tree may not be what you meant.

Some elements also restrict what may go inside them. A <p> may only contain text-level content, so a <div> or a list inside a paragraph is invalid: the browser closes the paragraph just before it. A <ul> may only contain <li> children. The block and inline section of the next page explains the categories behind these rules.

Void elements

A void element is an element that can never have content, and therefore has only a start tag. HTML has exactly thirteen:

area  base  br  col  embed  hr  img  input  link  meta  source  track  wbr
<p>Kestrel Energy<br>Harbor Street 12<br>3011 AB Rotterdam</p>
<hr>
<p>
  <label for="meter-id">Meter number</label>
  <input id="meter-id" name="meter-id">
</p>

<br> is a line break, <hr> a thematic break (drawn as a horizontal line), and <input> a form field. None of them gets an end tag: </br> is an error (browsers happen to treat it as another <br>), and </input> is simply ignored.

You will also see void elements written with a slash, such as <br /> or <meta charset="utf-8" />. That habit comes from XHTML. In HTML the slash is allowed on void elements and does nothing; many code formatters add it. On other elements it does not close anything: <div /> is just a start tag, and the rest of the page ends up inside that div.

Attributes

An HTML attribute is a name-value pair in a start tag that sets an option or adds information to an element: where a link goes, which image to load, what type of input field to show.

<a href="/outages" title="Current outages in your area">Outage map</a>
<img src="logo.svg" alt="Kestrel Energy" width="160" height="40">
  • Write the value in double quotes (single quotes work too). Quotes may be left out when the value has no spaces or special characters, but always using them avoids a whole class of bugs.
  • Attribute names are case-insensitive; write them in lowercase.
  • The order of attributes does not matter, and each name may appear only once per element.

Attributes every element accepts

Most attributes belong to specific elements (href to links, src to images). A set of global attributes works on every element:

Attribute Purpose
id A unique name for one element in the page: target of #links, the for of a label, a hook for scripts. Never use the same id twice in a page.
class One or more space-separated names, used by CSS and scripts to treat groups of elements alike
lang The language of the element's content
title Advisory text, shown by most browsers as a tooltip on mouse hover; not available to touch or keyboard users, so never put essential information only there
hidden Hides the element completely
style Inline CSS for this one element
data-* Custom data for scripts and CSS, such as data-meter-id="E-4402"

Boolean attributes

A boolean attribute is an attribute that is on when it is present and off when it is absent. Its value does not matter: you write just the name, or (in a few styles) the name repeated as the value.

<p>
  <input type="checkbox" id="alerts" checked>
  <label for="alerts">Email me about outages</label>
</p>
<p><button type="button" disabled>Submit reading</button></p>
<details open>
  <summary>Where is my meter?</summary>
  <p>Usually in the meter cupboard near the front door.</p>
</details>

checked ticks the box, disabled grays out the button, and open shows the content of the <details> element (click the summary to fold it).

The head and the body

The <html> element has exactly two children. The head element (<head>) holds information about the document that is not displayed as content: its title, character encoding, viewport setting, links to stylesheets, scripts and metadata for search engines and link previews. The <body> holds the content itself.

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Outage map — Kestrel Energy</title>
  <meta name="description" content="Current power and gas outages by postal code.">
  <link rel="icon" href="/favicon.svg">
  <link rel="stylesheet" href="/css/portal.css">
  <script src="/js/map.js" defer></script>
</head>

The <title> is required in normal documents and appears in the browser tab, bookmarks and search results. The description, the favicon and the other metadata are covered in Metadata, Sharing and Search Basics. Scripts are explained in the JavaScript topic.

Declaring the character encoding

A file on disk is only bytes. The browser must know which character encoding was used to turn those bytes back into letters. Without that, "€", "é" or "ł" come out as garbage such as "€", known as mojibake. The declaration is one line:

<meta charset="utf-8">

The value must be utf-8 (the case does not matter), and the standard requires the whole element to sit within the first 1024 bytes of the document, because the browser only looks that far before it starts decoding. Put it first in the head, before the title. And save the file itself as UTF-8: the declaration only describes the file, it does not convert it.

A charset in the server's Content-Type header takes priority over the meta tag. If a page shows mojibake although its meta tag says UTF-8, check what the server sends.

The viewport meta tag

The viewport meta tag tells mobile browsers to lay the page out at the real width of the screen instead of pretending to be a desktop screen:

<meta name="viewport" content="width=device-width, initial-scale=1">

Without it, phone browsers lay out the page as if the screen were about as wide as a small desktop window (often around 980 CSS pixels) and then shrink the whole page to fit, so text becomes tiny and your mobile styles never apply. width=device-width uses the device's width in CSS pixels, which is not the number of hardware pixels (see pixel density); initial-scale=1 starts without zoom.

Character references and comments

Some characters have a special meaning in HTML. A < starts a tag and an & starts a character reference (often called an HTML entity). To show them as text, write them as references:

Write To get Why
&lt; and &gt; < and > Angle brackets would be read as tags
&amp; & The ampersand starts a reference
&quot; " A quote inside a double-quoted attribute value
&nbsp; a non-breaking space Keeps two words on the same line
&copy;, &euro;, &mdash; ©, €, — Named references for common symbols
&#8364; or &#x20AC; € Any character by its Unicode code point (decimal or hex)
<p>Use &lt;p&gt; for paragraphs &amp; &lt;br&gt; for line breaks.</p>
<p>Day rate: &euro;0.32 per kWh &mdash; price fixed until 1&nbsp;January.</p>
<!-- Night rate hidden until the new tariff is approved -->
<p>&copy; 2026 Kestrel Energy</p>

In a UTF-8 page you can type €, © or — directly, so named references are mainly needed for <, & and quotes. Escaping these characters matters for security too: when a site puts user input into a page without turning < into &lt;, an attacker can inject their own tags and scripts, which is cross-site scripting.

The third line of the example is a comment: everything between <!-- and --> is not displayed. Comments are useful for notes to other developers, but they are not secret: anyone can read them with "View page source". Never leave passwords, internal hostnames or "temporary" workarounds in HTML comments.

Optional tags and writing it out in full

HTML lets you leave out some tags when the browser can work out where they belong. The start tags of <html>, <head> and <body> are optional, and so are many end tags, such as </p> and </li>. The elements still exist in the DOM: the parser creates them on its own. This is valid HTML:

<!doctype html>
<title>Outages</title>
<p>No outages reported.
<p>Last checked at 09:15.

It works, but writing every element out in full is easier to read, to edit and to style, and it avoids surprises: a paragraph without </p> runs on until the next block-level element, so an image or a link written after it quietly becomes part of the paragraph. The examples in this topic always close every element.

To catch mistakes you cannot see, run your page through an HTML validator now and then; see Debugging HTML and CSS with Developer Tools.