A page is a tree, written as text
HTML is not a programming language. There is no logic in it, nothing is calculated and nothing loops. It is a way of writing down what the parts of a document are: this is a heading, this is a paragraph, this is a link to somewhere else.
The browser reads that text and builds a tree of objects from it, called the DOM. CSS then styles that tree and JavaScript changes it. So every mistake you make in HTML is inherited by everything downstream.
An element
An element is usually an opening tag, some content, and a closing tag. The tag name says what the content is.
<p class="lede">The smallest complete element.</p>
<!-- ^ ^ ^ ^
| | | closing tag
| | content
| attribute (name="value")
tag name -->
Attributes
Attributes go in the opening tag and configure the element. Some are global and work almost anywhere (id, class, title, hidden, lang, data-*); others belong to one element (href on a link, src on an image).
Quote every attribute value. Unquoted values work until the value contains a space, and then they fail in a way that is hard to see.
idmust be unique in the document — it is the address of one element.classcan repeat and an element can carry several, separated by spaces.data-anythingis yours: the browser ignores it, JavaScript reads it fromelement.dataset.
Elements with no content
A few elements have nothing inside them, so they have no closing tag: <img>, <br>, <hr>, <input>, <meta>, <link>. These are called void elements. You may write <br /> — the slash is allowed and ignored, it is not required.
Nesting
Elements nest, and the order must match. Open A, open B, close B, close A. Browsers repair some mistakes silently, which is worse than an error: your page renders, but not as the tree you intended.
<p>A <strong>bold</strong> word.</p> <!-- correct -->
<p>A <strong>bold</p></strong> <!-- crossed over: the browser will rebuild this -->
The document
Every page has the same skeleton. The doctype is not a tag — it tells the browser to use standards mode. Without it you get quirks mode, where old layout bugs are emulated on purpose.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My page</title>
</head>
<body>
<h1>My page</h1>
<p>The first thing anybody reads.</p>
</body>
</html>
head and body
<head> is information about the document: its title, its character set, links to stylesheets, metadata for search engines and social cards. None of it is drawn on the page.
<body> is the document itself — everything a reader sees.
<meta charset="utf-8"> makes accented and non-Latin characters render correctly, and it must come within the first 1024 bytes. <meta name="viewport" …> stops phones from rendering the page at desktop width and shrinking it.
Comments and whitespace
<!-- like this --> is a comment. It is not shown, but it is downloaded — never leave anything private in one.
Any run of spaces, tabs and newlines collapses into a single space when rendered. You can indent your source however you like; it will not change the output.