HTML Document Structure
The parts of every HTML page: the doctype, html, head and body, elements, tags and attributes, nesting rules, void elements, character encoding and the viewport meta tag.
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; thelangattribute 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 namepplus 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:
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 |
|---|---|---|
< and > |
< and > | Angle brackets would be read as tags |
& |
& | The ampersand starts a reference |
" |
" | A quote inside a double-quoted attribute value |
|
a non-breaking space | Keeps two words on the same line |
©, €,
—
|
©, €, — | Named references for common symbols |
€ or € |
€ | Any character by its Unicode code point (decimal or hex) |
<p>Use <p> for paragraphs & <br> for line breaks.</p>
<p>Day rate: €0.32 per kWh — price fixed until 1 January.</p>
<!-- Night rate hidden until the new tariff is approved -->
<p>© 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 <, 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.