HTML structure for beginners
Before we start
Every single web page you visit, from Wikipedia to a complex application, rests on an invisible skeleton: HTML. Before you think about colours, animation or dynamic features, it is this load-bearing structure that arranges the information logically.
For that reason, rather than memorising dozens of tags, we will concentrate on how a page is structured. Writing clean, tidy code is not about looking good: it is what makes a site usable by everyone and easy to find in search engines. Here we cover the basics you need to start, but the real progress comes with practice: experiment and write your own code from day one, one step at a time.

1. How HTML thinks (and what it is not)
Let's start with something fundamental: HTML is not a programming language. It performs no calculations, makes no logical decisions and cannot do arithmetic. HTML stands for HyperText Markup Language.
Its only job is to “mark up” plain text so it can tell the browser: “This piece is a heading, this sentence is a paragraph, and this element is a button”.
When you write an HTML file and open it, the browser reads it and builds a tree structure called the DOM (Document Object Model). Picture it as a branching set of boxes: the main box contains smaller boxes, which in turn contain text or further boxes. CSS and JavaScript will later act on exactly this tree. Without solid HTML they would have nothing to style or animate.
Now that we have a sense of how the browser organises information into boxes (the DOM), let's build our first box. To do that, we start from HTML's basic building block: the tag.
2. Anatomy of a tag, and the nesting rule
To give a web page structure, HTML uses tags — literally, labels. Imagine you have some plain text and you want to tell the browser: “This piece of text is a paragraph, while this other one is a heading”. To do that, you stick a label before and after the text.
These labels are written between angle brackets (the less-than < and greater-than > symbols) and, in most cases, always work in pairs:
- The opening tag (the start): marks where our element begins. You write the label's name between the angle brackets. To start a paragraph, for instance, we use
<p>(the “p” stands for paragraph). - The content: everything enclosed between the opening and closing labels (the paragraph's text, for example).
- The closing tag (the end): tells the browser where the element finishes. It is identical to the opening one but contains a forward slash
/placed straight after the first angle bracket, like this:</p>. Think of the slash as a stop sign.
These three parts together (opening tag + content + closing tag) form what is known as an HTML element.
marks where our element begins. To start a paragraph we use the <p> tag.
everything enclosed between the opening and closing labels.
tells the browser where the element ends. It carries a slash (/) before the name, as in: </p>.
Tags can also carry attributes: extra pieces of information placed exclusively inside the opening tag. Attributes specify an element's characteristics or behaviour (a link's destination, an image's source) and are always written in the form attribute-name="value" — a key="value" pair.
Let's look at a link (the <a> tag):
the address or parameter to point at (here, href).
the specific value tied to the attribute, always written in quotation marks ("https://codedge.it").
In this example:
- The key (the attribute's name) is
href, which tells the browser: “I want to set a destination web address”. - The value is
"https://codedge.it"(always in quotation marks after the equals sign=), which defines the specific web address.
A few rare tags contain no text and enclose nothing, so they need no closing tag. These are called empty tags (or self-closing tags). Examples are <img> for showing an image and <br> for a line break.
The nesting rule
In HTML, putting one tag inside another is called nesting. Picture the structure as a set of Russian dolls: a smaller box has to sit entirely inside a bigger one.
There is one very simple golden rule that keeps you from ever getting it wrong:
The last tag you open must always be the first one you close. You can never cross tags over!
To grasp this easily, think of the brackets you use when writing by hand. If you open a round bracket ( and then a square one [, you must close the square one first and the round one after. You cannot cross them like this: ( [ ) ].
Let's compare the wrong structure with the right one directly.
❌ The most common mistake (crossed tags)
Imagine you want to put a word in bold (with <strong>) inside a paragraph (with <p>):
<p>This text is <strong>very important.</p></strong>Draw it as boxes and the mistake jumps out:
Here the boxes slice through each other. That forces the browser to guess, and it can break the page's appearance.
✅ The correct structure (tidy boxes)
To fix it, we make sure the smaller box (the bold) closes before the bigger one (the paragraph):
<p>This text is <strong>very important.</strong></p>Now the boxes sit perfectly one inside the other:
The hierarchy is now clean, logical and trivially easy for any browser or screen reader to interpret.
Modern browsers do their best to “fix” nesting errors on their own, but making them do so slows them down and forces them to guess your intentions. That often leads to unexpected visual bugs, problems on mobile devices, or difficulties for people using screen readers.
Now that you know how to write a single tag, how to set attributes and how to fit boxes inside one another without mistakes, let's put all the pieces together. In the next chapter we look at the starting skeleton shared by every web page that exists.
3. The boilerplate: the starting tags
The previous chapters covered how the browser builds the tree and how tags nest without crossing. Now we open a real file.
The page's skeleton is the whole HTML document: every tag the browser will turn into the DOM. The boilerplate is narrower. It is only the starting kit — the few main tags already nested in the right place. In VS Code you type ! and press Enter to get it.
The file starts with a declaration (DOCTYPE), then the root tag <html>. Inside it sit two boxes: <head>, invisible on the page, with the meta tags and title; and <body>, what the visitor sees. The widget below takes them apart one at a time.
Interactive example
The boilerplate, piece by piece
Click the four parts. Watch the nesting: html holds head and body; head holds the meta tags and title.
It must be the first line, before <html>. It tells the browser to read the file as modern HTML5. Leave it out and the browser may slip into quirks mode.
<html> is the root tag.The whole document sits inside this box: first <head>, then <body>. It is the outermost tag of the boilerplate, not the skeleton by itself.
<head> live the meta tags and title.The <meta> tags talk to the browser: charset="UTF-8" so letters and emoji read correctly, viewport so phones scale the page. <title> is the tab text — also used in bookmarks and as the default search-result heading. None of this is drawn in the page body.
<body> is the sibling of <head>.Both sit inside <html>, never inside each other. Headings, paragraphs, images, forms: if the visitor should see it, it belongs here.
lang="en"is not a tag: it is an attribute of<html>, aname="value"pair in the opening tag only. It tells the browser, search engines and screen readers that the page is in English. Attributes never close.<meta>is a tag, and an empty one: it has no content and no closing tag. What it does depends on its attributes.charset="UTF-8"tells the browser how to read the file's characters, so accented letters and emoji do not come out as garbage.- The second
<meta>uses two attributes:name="viewport"names the setting,content="…"gives the value. Together they tell phones not to pretend the page is a desktop screen shrunk down.width=device-widthmatches the real screen;initial-scale=1.0starts at 100% zoom.
Leave all three as they are. We will come back to attributes later; these three are already doing their job.
The rule to take away is the one from chapter 2: the last tag you open is the first you close. <head> and <body> sit entirely inside <html>; the meta tags and title sit entirely inside <head>.
Now we step into the <body> and start filling it. The first brick is text.
4. The page's text: paragraphs and headings
What is the <body> tag?
The <body> tag is, to all intents and purposes, your website's visible canvas. Everything a user sees, reads, clicks or interacts with inside the browser window — text, headings, images, video, buttons, navigation menus, contact forms — must go inside this box, without exception.
In the previous chapter we saw that an HTML page is divided into two main areas: the <head>, which contains information for the browser, and the <body>, which contains what appears on the page. To tell them apart more easily, think of a paper letter:
- The
<body>is the sheet of paper inside: it contains the actual message, the written words, drawings or enclosures. It is the part the reader sees and consults. - The
<head>, by contrast, is comparable to the envelope: it contains information needed to identify and handle the letter, but not the message intended for the reader.
There are three golden rules to keep in mind whenever you work with the <body>:
- Uniqueness: there can be one and only one
<body>tag in an HTML file. You cannot have “two bodies”. - Position: it opens straight after the
<head>closes (that is, after</head>) and closes right before the page's final closing tag (before</html>). - Nothing outside the edges: writing code or placing visible tags outside
<body>(below its closing tag, for instance) is a serious syntax error. Modern browsers try to patch it up by quietly forcing the code back into the body, but that creates bugs which are very hard to track down in your layout (CSS) and your interactive behaviour (JavaScript).
Now that it is clear what this space is and why it exists, let's start filling it. The most common case is text.
If you write bare sentences in the <body> and hit Enter in the editor, the browser joins them into one line: line breaks in the file do not count. To make a block you need the <p> tag.
To break up a long text you need headings, from <h1> to <h6>. The number is not the font size: it is the depth in the page's table of contents. The widget below shows the same words in four structures.
Interactive example
The same words, four structures
Click each view. On the left is the code; on the right is what the browser actually shows — and, for headings, the outline it builds.
<body> Cookery course This is the first sentence. This should be a separate sentence. </body>
<body> <p>This is the first sentence.</p> <p>This is a separate sentence.</p> </body>
<h1>Cookery course</h1> <h2>The equipment</h2> <h3>Knives</h3> <h3>Pans</h3> <h2>The first recipes</h2>
<!-- “h2 is too big, I'll use h4” --> <h1>Cookery course</h1> <h4>The equipment</h4>
Cookery course This is the first sentence. This should be a separate sentence.
This is the first sentence.
This is a separate sentence.
Cookery course
The equipment
Knives
Pans
The first recipes
Cookery course
The equipment
The code has three line breaks, yet the page still shows one line. The browser ignores those returns: to it this is a single lump of text. To split the line on screen you need a tag, not the Enter key.
Same words as before, but each sits in a <p>. The browser stacks them, with a little air in between. The tag decides the blocks: you can hit Enter ten times in the code and it is still one paragraph.
<h1> is the book title. <h2> is a chapter. <h3> is a section of that chapter. On the right you see the outline that comes out: no gaps, each heading in its place. That h1 looks bigger is only the browser's default.
The browser draws it smaller, so it is tempting to use it instead of h2. But 4 means “fourth step”, not “small type”. Here we jump from h1 to h4: the outline is missing the chapter and the sub-chapter, like a book that goes from 1 to 4. If h2 looks too big, keep h2: you change the size later, with CSS.
The paragraph: <p>
A <p> encloses one complete thought. You already saw that the tag, not Enter, creates the block. One more rule, because the browser will not warn you:
Write <p>Text <p>more text</p></p> and the browser reports no error: it closes the first paragraph by itself as soon as it meets a second one, and you end up with a structure different from the one you had in mind. A <p> cannot contain other paragraphs or structural elements such as headings or <div> elements. We will see why in chapter 5.
Headings: from <h1> to <h6>
There are six heading tags. The widget showed the idea: the number is the depth in the outline, the same way a book's table of contents is numbered. That outline is what Google and screen readers use to jump from section to section.
The three rules of headings
- One
<h1>per page. It is the document's title, so there is only one, like the title of a book. - Do not skip levels. After an
<h2>you can use an<h3>, not an<h4>directly: it would leave a hole in the outline. - Choose the level by logic, never by how big it looks on screen. The browser gives headings a default size purely for convenience: you will change that size with CSS, without touching the tag.
“This <h2> comes out too big, I'll use an <h4>.” h4 is not a small h2: it is a fourth-level heading. Two steps vanish from the outline. Keep the right level and change the size with CSS.
Headings and paragraphs establish a page's overall frame. You may have noticed, however, that these elements start on a new line, while other tags can mark individual words without interrupting the text. In the next chapter we compare these two behaviours — block and inline — before exploring the tags that work inside sentences.
5. Block and inline elements: how tags flow on the page
Before learning more tags, you need a map of how the browser lays them out on the page.
A block element starts on a new line and takes the full available width. That is why two paragraphs or two headings sit one below the other, even when the text is short.
An inline element does not start a new line: it takes only as much space as its letters need and stays in the sentence. It marks a word without breaking the paragraph. Examples: <strong>, <em>, <code>, <a>.
Inline lives inside a block, not the other way around. And <br> is not for making space between two boxes: it is for when the line break is part of the text, as in an address. The widget below shows these four points.
Interactive example
Block or inline
Click each view. The dashed boxes are the full row. The highlighted words stay in the sentence.
<p>Short.</p> <p>Also short.</p>
<p> The <strong>title</strong> tag is <code>mandatory</code>. </p>
<p>First block.</p> <p> Inside here, <strong>I stay inline</strong>. </p>
<!-- wrong: making space --> <p>Above</p> <br><br><br> <p>Below</p> <!-- right: the break is the content --> <p> Codedge Ltd<br> 12 Roma Street </p>
The title tag is mandatory.
· · · three <br>
Codedge Ltd
12 Roma Street
The text is short, yet each <p> starts below the one before it. The browser gives it the full width, like two boxes stacked. Headings work the same way.
<strong> and <code> do not start a new row. They mark a word, and the rest of the phrase continues beside it. They take only as much space as the letters need.
The paragraphs are the boxes. Inside a box, marked words stay in a line. Not the other way around: a <p> cannot sit inside a <strong>.
<br> is not for making space.Three <br> in a row to push two pieces apart is the beginner mistake. Use <br> when the new line is part of the text: an address, a verse. Space between boxes is CSS's job.
The block elements you will actually use
| Tag | Meaning | Default rendering |
|---|---|---|
| <p> | A paragraph of text. | A new line, with space above and below |
| <h1>…<h6> | Headings, ordered by hierarchy level. | A new line, with prominent text |
| <div> | A neutral container with no semantic meaning. | A new line, using the full available width |
| <ul> <ol> <li> | Unordered lists, ordered lists and their items. | A vertical list with bullets or numbers |
| <blockquote> | An extended quotation from another source. | A separate block, usually indented |
| <pre> | Preformatted text that preserves spaces and line breaks. | A block in monospace type |
| <figure> | Self-contained content, such as an image with a caption. | A separate block with margins |
| <header> <nav> <main> <section> <article> <aside> <footer> | The semantic regions that organise the page. | A new line, using the full available width |
| <form> | A set of fields for collecting data from the user. | A separate block |
| <hr> | A thematic break between two parts of the content. | A horizontal line on a new row |
Inline elements
Besides those already seen, <span>, <abbr> and <img> also stay inline.
Here, “block” and “inline” describe how the browser normally lays elements out. You will be able to change their visual rendering with the CSS display property, but that does not change their meaning or the rules governing what content they may contain.
Marking up words with inline tags
Now let us focus on the inline elements that give one or more words a precise meaning without interrupting the paragraph's flow.
Before we look at the individual tags, there is a golden rule to understand: HTML exists to define text's meaning (its semantics), not its appearance. Today, any purely visual effect — changing a colour, enlarging a character, applying complex styling — is handled far more effectively and professionally with CSS.
Use the tags in this chapter only when you want to give a word a precise meaning (to make clear to Google or a screen reader that a term is crucial, or that it is an abbreviation). If your aim is purely decorative or visual, reach for CSS instead.
<p>Remember: the <code><title></code> tag is <strong>mandatory</strong> on every page.</p>The sentence stays a single sentence: <strong> and <code> do not create a new block, they mark up part of existing text.
The inline tags you will actually use
| Tag | Meaning | Default rendering |
|---|---|---|
| <strong> | Content of strong importance: a warning, a critical word. | Bold |
| <em> | Emphasis: the word the stress falls on when reading the sentence aloud. | Italic |
| <code> | A fragment of code or a technical name (a file, a command, a tag). | Monospace |
| <mark> | Text highlighted because it is relevant in this context (a searched-for term, say). | Yellow background |
| <a> | A link. The most important inline tag: chapter 7 is devoted to it. | Blue underlined text |
| <abbr> | An abbreviation; the expanded meaning goes in the title attribute. | None, or a dotted underline |
| <time> | A date or a time, machine-readable via datetime. | None |
| <small> | Side notes: copyright, disclaimers, legal small print. | Smaller type |
| <del> <ins> | Text removed and text added relative to an earlier version. | Struck through / underlined |
| <sub> <sup> | Subscript and superscript: chemical formulas, powers, footnotes. | Lowered / raised text |
| <span> | No meaning at all: a neutral container, covered in chapter 11. | None |
<p>
<abbr title="HyperText Markup Language">HTML</abbr> was born in
<time datetime="1991">1991</time>. The formula for water is
H<sub>2</sub>O and the price is <del>£20</del> <ins>£15</ins>.
</p><strong> or <b>? <em> or <i>?
<b> and <i> also exist, and on screen they give exactly the same result (bold and italic). They have not been removed from the standard, but they mean something different: they mark text as visually distinct without attaching importance to it (a scientific name in italics, say, or an article's keywords in bold).
If the word is important or needs emphasis when read, use <strong> and <em>: a screen reader can change its tone of voice, and a search engine takes it into account. If you only want a visual effect, use neither: that is CSS's job.
Breaking lines for real: <br> and <hr>
We said that pressing Enter in the code does not break the line. When the line break is part of the content — a postal address, the lines of a poem — you use the empty tag <br>:
<p>
Codedge Ltd<br>
12 Roma Street<br>
00100 Rome
</p><br> to create spaceStacking three <br> in a row to push two blocks apart is the most recognisable beginner mistake there is. Space between elements is controlled with the CSS margin property. <br> is only for when the new line is part of the text.
The <hr> tag, also empty, marks a thematic break between two parts of the content (the browser draws it as a horizontal line). Same principle: if all you want is a decorative line, use CSS.
Writing special characters: entities
What if you want to display the < symbol itself on screen? The browser would think you were opening a tag. That is why HTML entities exist: sequences beginning with & and ending with ;.
| You write | You get | When you need it |
|---|---|---|
| < | < | Showing HTML code inside a page |
| > | > | As above |
| & | & | The ampersand, which would otherwise start an entity |
| | non-breaking space | Keeping two words on the same line (e.g. 10 kg) |
| © | © | The copyright symbol in a footer |
Accented letters and emoji need no entities: the file just has to declare <meta charset="UTF-8">, as in the boilerplate from chapter 3.
Comments
You can leave notes in the code that the browser ignores entirely, by wrapping them in <!-- and -->:
<!-- Offers section: update every Monday -->
<p>20% off the whole catalogue.</p>They do not appear on the page, but they stay in the file: anyone can read them with “View page source”. Do not put passwords, confidential notes or remarks about clients in them.
Block and inline do not determine nesting by themselves
Screen layout alone does not tell you which tags may go inside other tags. Each HTML element allows specific kinds of content: a <p>, for example, may contain text and elements that mark up words, but not structures such as headings, other paragraphs or <div> elements.
Do not place structural elements such as <h2>, <p> or <div> inside <p>, <span>, <strong>, <em> or other tags used to mark up stretches of text. Code such as <p><div>...</div></p> is invalid: the browser tries to correct it and may produce a structure different from the one you wrote.
The <a> tag is a special case: it can wrap either a stretch of text or, when the context permits it, a whole block of content. This makes it possible to turn an entire card into a link rather than only its words:
<a href="/products/mug/">
<h3>Codedge mug</h3>
<p>Glazed ceramic, 350 ml.</p>
</a>You can now recognise the two most common layout behaviours, use tags that mark up text and avoid confusing visual rendering with document structure. One cross-cutting piece is missing: the attributes you can put on tags and will use constantly in the coming chapters.
6. Universal attributes: giving tags an identity
So far we have seen attributes that only work on certain tags: href, for example, makes sense on a link but not on a paragraph. HTML also provides global attributes: you can write them on any element.
The two that matter most are id and class. Both are labels, so you can find an element later. The only difference is how many times the label may appear:
idis a unique name, like a registration number. It may exist only once on a page.classis a group name. The same class can sit on as many elements as you like, and one element can have several classes separated by a space.
On their own they change neither colour, size nor position. They prepare the ground for a link, a stylesheet or some JavaScript. In the HTML you write only the name: no # and no .. The widget below shows these four points.
Interactive example
id and class
One name for one element. One name for a group. Neither paints the page.
<p id="contact">Where to find us</p> <p class="notice">Shipping is fine.</p> <p>No label.</p>
<h2 id="contact">Where to find us</h2> <p>12 Roma Street.</p>
<p class="notice">Shipping is fine.</p> <p class="notice urgent">Last day to order.</p> <p>Not in the group.</p>
<!-- wrong --> <h2 id="#contact"> <p class=".notice"> <!-- right --> <h2 id="contact"> <p class="notice">
An id or a class does not change colour, size or position. They are names, ready for a link, a stylesheet or some JavaScript to look them up later.
id is a unique name.Like a registration number: it belongs to one element only. On this page there can be only one contact. Use it when you need that precise spot: a section to jump to, a field to label.
class is a group name.The same class can sit on as many elements as you like. A space separates two classes: notice urgent is not one name with a space in it, it is two groups. When in doubt, use class.
CSS will later look them up as #contact and .notice. Those symbols are the search, not the name. Put them in the tag and the name becomes wrong. The one exception is a link to an id: href="#contact".
1. The id attribute: a unique name
id assigns a name identifying a single element on the page. You write it like any other attribute, inside the opening tag:
<h2 id="contact">Where to find us</h2>
<p>We are at 12 Roma Street, open Monday to Friday.</p>What is a unique name good for?
- In HTML: for creating links that jump to a specific point on the page (anchors, which we will see in the next chapter) and for tying a form's label to its input.
- In CSS: for styling that one element.
- In JavaScript: for reaching that specific element and changing it, in response to a click for instance.
id:- It must be unique on the page: repeat it and the page still displays, but links and JavaScript will only find the first element, and the behaviour becomes unpredictable.
- It cannot be empty and cannot contain spaces: write
contact-section, notcontact section. - It is case-sensitive:
Contactandcontactare two different ids. - To stay safe, use only lowercase letters, digits and hyphens, and always begin with a letter.
2. The class attribute: a group label
class groups elements under a common category. The same class can appear on as many elements as you like:
<p class="notice">Shipping is running normally.</p>
<p class="notice">Last day to order!</p>
<p>This paragraph has no classes: it is not part of the group.</p>An element can also belong to several groups at once: just write the names separated by a space. That is not “one class with a space in it”, it is two distinct classes:
<p class="notice urgent">Deliveries suspended today.</p>What is grouping good for?
- In CSS: for giving every element in the group the same appearance by writing the rule once.
- In JavaScript: for acting on every element in the group with a single instruction.
- For whoever reads the code: a name like
product-cardsays at a glance what role that element plays.
# and . are not written in the HTML!When you meet CSS later, you will see ids preceded by a hash (#contact) and classes preceded by a dot (.notice). Those symbols exist only to call them up from outside: they tell the browser “find the element with this id” or “find the elements with this class”. In the HTML tag you write only the name.
- ❌ Wrong:
<h2 id="#contact">—<p class=".notice"> - ✅ Right:
<h2 id="contact">—<p class="notice">
The one exception you will see in HTML is the href of an internal link, which points at an id and therefore uses the hash: <a href="#contact">.
If you need to reach that precise element (a section to point a link at, a form field), use id. If you are describing a type of element that might repeat (a button, a highlighted box, a product card), use class. When in doubt, use class: you can never accidentally create duplicates.
Other useful global attributes
Besides those two, there are others you will meet often:
| Attribute | What is it for? | Practical note |
|---|---|---|
| lang | Declares the text's language, useful to search engines and screen readers. | Put it on the <html> tag for the whole page (e.g. lang="en") and on a single element only where the language changes. |
| title | Shows a small explanatory tooltip when the mouse hovers. | Do not put essential information in it: on phones, and for keyboard users, it often never appears at all. |
| hidden | Tells the browser the element is not relevant yet: it is neither displayed nor read out by screen readers. | It takes no value, you just write it in the tag: <p hidden>...</p>. |
| style | Applies CSS rules directly to that single tag. | Best avoided: it mixes presentation with structure and cannot be reused. Use class and a stylesheet. |
For instance, if you drop a phrase or a quotation in another language into an English page, it is good practice to flag it like this:
<p>The company's motto is <span lang="it">chi va piano va sano</span>.</p>That way the screen readers used by people with visual impairments will pronounce the phrase in Italian, rather than reading it as if it were English.
Now that you know how to give an element an id, we can tackle the tag that turned the web into a web: the link.
7. Links: connecting pages
The hyperlink is what turns isolated pages into a site, and sites into a web. You create one with the <a> tag (anchor) and its href attribute (hypertext reference), which gives the destination.
<a href="https://codedge.it">Go to the Codedge site</a>The text between the two tags is the visible, clickable part; href is the address, which the user does not see. An <a> without an href is not a link: it is just text.
The possible destinations
| Type | Example href | Effect |
|---|---|---|
| Absolute URL | https://google.com | Goes to another site. You need the full address, with https://. |
| Root-relative path | /contact/ | Goes to a page on your own site, starting from its root. |
| Relative path | products.html | Goes to a file, looking for it from the current page's folder. |
| Anchor | #contact | Scrolls to the element with id="contact" on the current page. |
| mailto:[email protected] | Opens the mail program with the recipient already filled in. | |
| Telephone | tel:+390612345 | On a phone, starts the call. |
Relative paths, explained once and for all
This is where everyone trips up: when looking at a project, it feels natural to read every path from the main site/ folder. The browser, however, always starts from the folder holding the page that contains the link.
- 1
Start in
site/index.html. - 2
We want to reach
site/about.html. - 3
Both files are in the exact same folder. No need to go up or enter other folders: just write the file name directly.
- 1
Start in
site/index.html. - 2
We want to reach
site/blog/first-post.html. - 3
The target file sits inside a subfolder. We must first enter
blog/by writing its name, then add the file name.
- 1
Start in
site/blog/first-post.html. - 2
We want to reach the main home page
site/index.html. - 3
We must first exit the
blog/folder to go up tosite/. We write../to go up one folder, where we findindex.html.
- 1
Start in
site/blog/first-post.html. - 2
We want to reach the image
site/images/logo.png. - 3
First, we exit `blog/` using
../. Once we are insite/, we enter theimages/folder and selectlogo.png.
- 1
Start in
site/blog/first-post.html. - 2
We want to reach the blog home page
site/blog/index.html. - 3
Both are in the exact same
blog/folder. We don't need to exit with../: just target the file name directly.
blog/index.html means “the blog folder inside the one I am in now”. /blog/index.html, with the leading slash, means “start from the site's root, wherever I happen to be”. Root-relative paths are more convenient for navigation menus, because they work identically from any page.
Opening in a new tab
The target="_blank" attribute opens the destination in a new tab, leaving the starting page open.
<a href="https://github.com" target="_blank" rel="noopener noreferrer">
Open GitHub in a new tab
</a>The rel="noopener" attribute stops the destination page manipulating the originating one via JavaScript; noreferrer also avoids telling it where the visitor came from. Recent browsers apply noopener themselves with target="_blank", but writing it remains the right habit and covers older browsers.
target="_blank"Firing off tabs takes the back button out of the user's control. Use it for external links or documents to consult alongside, never for navigation within your own site.
Writing good link text
Screen reader users can ask for a list of every link on the page, read out of context. A list of twelve “click here” entries is unusable.
| Avoid | Better |
|---|---|
| For the price list click here. | See the 2026 price list. |
| Read more | Read the article on HTML semantics |
Link text should describe its destination even when read on its own. That is the accessibility rule for links, and it doubles as good SEO practice.
Your pages now talk to each other. Let's add visual content.
8. Images and media
Images are inserted with <img>, an empty tag: it encloses nothing and has no closing tag, so all the information travels through attributes.
<img src="/images/cat.jpg"
alt="Ginger cat asleep on a grey sofa"
width="800" height="600">| Attribute | What it does | Required? |
|---|---|---|
| src | The image file's path. It follows the same rules as the links in chapter 7. | Yes |
| alt | The alternative text, read out when the image cannot be seen. | Yes |
| width height | The file's real dimensions in pixels. | Strongly recommended |
| loading | With lazy, the image is downloaded only when it is needed. | No |
Writing the right alt
The alt attribute is read by screen readers, appears if the file fails to load, and is used by search engines. There is no absolutely “right” text: it depends on the role the image plays on the page.
- The image carries information → describe the information:
alt="Chart: sales up 30% in 2025". - The image is inside a link or a button → describe the destination, not the picture:
alt="Back to the home page", notalt="logo". - The image is purely decorative → leave the attribute empty:
alt="". The screen reader then skips it, instead of reading out the file name. - The text is already written next to the image →
alt="", so the same thing is not announced twice.
alt is not the same as leaving it emptyWriting alt="" declares “this image is decorative, ignore it”. If the attribute is missing altogether, the screen reader does not know what to do and reads the file name, producing things like “image d s c underscore 0042 dot jpg”. Always write alt, even when it is empty.
Why you should give width and height
The browser draws the page before it has finished downloading the images. If it does not know how much space to reserve, it lays the text out and then makes it jump when the image arrives: that is the irritating layout shift that loses your place on the page. Give the file's real dimensions and the space is reserved immediately.
width and height declare the file's native dimensions, so the browser knows the aspect ratio. How big it actually appears on screen is decided with CSS.
Which format to choose
- WebP / AVIF: today's default choice. The same quality as JPEG and PNG in much lighter files.
- JPEG: photographs, where maximum compatibility matters.
- PNG: images with a transparent background, or flat-colour graphics.
- SVG: logos, icons and diagrams. A vector format: it stays sharp at any size and weighs almost nothing.
An image with a caption: <figure>
When an image has a caption, do not put it in an ordinary <p>: there would be no connection between the two. Use <figure> to group them and <figcaption> for the caption.
<figure>
<img src="/images/client-server-diagram.png"
alt="The browser sends a request to the server, which responds with the page"
width="600" height="400" loading="lazy">
<figcaption>Figure 1 — The request/response cycle between client and server.</figcaption>
</figure>The caption describes the image for people who can see it; alt replaces it for people who cannot. They are two different texts with two different purposes: do not copy one into the other.
Video and audio
Video and audio work the same way, with the controls attribute showing the playback controls:
<video src="/media/demo.mp4" controls width="640" poster="/media/preview.jpg">
Your browser does not support video. <a href="/media/demo.mp4">Download the file</a>.
</video>The text inside the tag appears only if the browser cannot play the file. The poster attribute gives the image to show before playback starts. Avoid autoplay: a video that starts by itself with sound is the fastest way to get the page closed.
Text and images are sorted. Let's move on to organising information into lists.
9. Lists
Lists are for whenever you have several items of the same kind. Not just bullet points: navigation menus, product galleries and search results are all, underneath, lists. Marking them as such tells screen readers how many items are still to come.
Unordered list: <ul>
For when the order of the items does not change the meaning. The container is <ul> (unordered list), each entry is an <li> (list item).
<ul>
<li>Flour</li>
<li>Sugar</li>
<li>Eggs</li>
</ul>Ordered list: <ol>
For when the order matters: instructions, rankings, steps. The browser handles the numbering; you do not write the numbers.
<ol>
<li>Heat the oven to 180 °C</li>
<li>Mix the ingredients</li>
<li>Bake for 40 minutes</li>
</ol>On <ol> you can use start="5" to begin from a different number, reversed to count backwards and type="a" to number with letters.
Lists inside lists
This is where the most common mistake hides. The child list goes inside the <li> it belongs to, not between one <li> and the next:
<ul>
<li>Drinks
<ul>
<li>Water</li>
<li>Wine</li>
</ul>
</li>
<li>Desserts</li>
</ul><li> goes inside <ul> and <ol>A <div>, a <p> or loose text as direct children of a list is invalid. Any content has to go inside an <li> first, and that <li> can then contain whatever you like.
Description list: <dl>
For term/explanation pairs: glossaries, FAQs, spec sheets. The container is <dl>, the term is <dt>, the description is <dd>.
<dl>
<dt>URL</dt>
<dd>The unique address of a resource on the web.</dd>
<dt>DOM</dt>
<dd>The tree the browser builds as it reads the HTML.</dd>
</dl>A <dt> can have several <dd> elements (one term with several meanings), and several <dt> elements can share a <dd> (synonyms).
The practical case: a navigation menu
A menu is a list of links. Written this way, a screen reader announces “list of 3 items” and lets the user skip the whole thing.
<ul>
<li><a href="/">Home</a></li>
<li><a href="/services/">Services</a></li>
<li><a href="/contact/">Contact</a></li>
</ul>The bullets and the vertical stacking are removed with CSS: looking like a horizontal bar is a visual choice, while the list remains the correct structure.
Lists organise data in one dimension. When there are two dimensions, you need tables.
10. Tables
A table is not a rectangle with lines. It is a way to organise facts that only make sense where a row meets a column: a plan's price, a train time, a comparison of two subscriptions. If you lift that number out of the grid and it still stands on its own, you probably did not need a table.
The container is <table>. Inside it sit the rows (<tr>). Each row holds cells: <th> names a column or a row, <td> holds the fact. The table's title is <caption>. <thead> and <tbody> split the header from the data.
In the nineties, layouts were built from invisible tables. Today that is a serious mistake: a screen reader announces rows and columns that do not conceptually exist, and the page becomes impossible to adapt to small screens. For layout there is CSS and the semantic tags in chapter 12.
Someone looking at a table crosses row and column with their eyes. Someone listening hears the cells one after another. That is why every <th> gets a scope: the screen reader can say “Basic, price: £9.99” instead of a list of numbers. The widget below picks up these four points.
Interactive example
A table, piece by piece
The same four points, one at a time.
<table> <caption>2026 plans</caption> <tr> <th>Plan</th> <th>Price</th> <th>Storage</th> </tr> <tr> <td>Basic</td> <td>£9.99</td> <td>10 GB</td> </tr> </table>
<table> <caption>2026 plans</caption> <thead> <tr> <th>Plan</th> <th>Price</th> </tr> </thead> <tbody> <tr> <th>Basic</th> <td>£9.99</td> </tr> </tbody> </table>
<table> <tr> <th scope="col">Plan</th> <th scope="col">Price</th> </tr> <tr> <th scope="row">Basic</th> <td>£10</td> </tr> </table>
<!-- a page, not data --> <table> <tr> <td>Logo</td> <td>Menu</td> <td>Article</td> </tr> </table>
| Plan | Price | Storage |
|---|---|---|
| Basic | £9.99 | 10 GB |
| Premium | £14.99 | 100 GB |
| Plan | Price |
|---|---|
| Basic | £9.99 |
| Premium | £14.99 |
| Plan | Price |
|---|---|
| Basic | £10 |
| Logo | Menu | Article |
£9.99 is not “a price” on its own. It is the Basic plan's price. If the number still makes sense after you pull it out of the grid, you probably do not need a table.
<th> names. <td> holds the fact.<caption> is the table's title. <thead> is the header row. <tbody> is the data. A <tr> is one row. The first column can be a <th> too: it names the row.
scope="col" on Plan and Price: that name applies to the column below. scope="row" on Basic: that name applies to the row to the right. So £10 is not a stray number: it is the Basic plan's price.
Logo, menu and article are not a row of data. A screen reader will still announce three columns. For layout use CSS and the semantic tags in chapter 12.
The full structure
| Tag | Role |
|---|---|
| <table> | The container for the whole table. |
| <caption> | The table's title. It goes straight after <table> and tells listeners what it is about. |
| <thead> | The header row (or rows). |
| <tbody> | The body, with the data. |
| <tfoot> | A final summary row — totals, for example. |
| <tr> | A row (table row). It contains the cells. |
| <th> | A header cell: it names a column or a row. |
| <td> | A data cell (table data). |
Cells also take three attributes. They are not tags: they are name="value" pairs written in the opening tag.
| Attribute | On which tag | Role |
|---|---|---|
| scope | <th> | scope="col" names a column; scope="row" names a row. It helps listeners, not viewers. |
| colspan | <th> or <td> | How many columns that cell spans. colspan="2" covers two. |
| rowspan | <th> or <td> | How many rows that cell spans. rowspan="2" covers two. |
The attribute that makes a table accessible
As the widget showed: scope="col" or scope="row" on every <th>. Two words per cell, and the list of numbers becomes a sentence.
Merging cells
colspan stretches a cell across several columns, rowspan across several rows:
<tr>
<td colspan="2">This cell spans two columns</td>
<td>Normal</td>
</tr>Use them sparingly: tables with too many merged cells become hard to read and hard to announce correctly.
A table will not shrink beyond a certain point. The standard solution is to wrap it in a container that scrolls horizontally — <div class="table-wrapper"> with overflow-x: auto in the CSS — instead of letting it burst out of the page.
You have now learned all the content building blocks. The question becomes: how do you group them? Let's start with the containers that mean nothing, so we can see why the ones that do mean something matter.
11. div and span: the neutral containers
Every tag we have seen so far says something: <p> is a paragraph, <ul> is a list, <th> is a header. Two of them say nothing at all, and that is exactly why they exist.
<div>is a block container with no meaning.<span>is an inline container with no meaning.
You will recognise the distinction: it is the one from chapter 5. A <div> behaves like a <p> (breaks the line, takes the full width), a <span> like a <strong> (stays within the line).
What they are for
Grouping elements so you can hook something onto them — nearly always a class for CSS, sometimes an id for JavaScript — when that grouping has no meaning of its own to convey. The widget below shows the four points.
Interactive example
div and span
A block box, a word in the sentence, the two together, and the mistake of using a div instead of a heading.
<div> <h3>Mug</h3> <p>Ceramic, 350 ml.</p> </div>
<p> Price: <span>£12.90</span> </p>
<div> <h3>Mug</h3> <p> Price: <span>£12.90</span> </p> </div>
<div>How to write HTML</div> <h1>How to write HTML</h1>
Mug
Ceramic, 350 ml.
Price: £12.90
Mug
Price: £12.90
How to write HTML
How to write HTML
<div> is a box. It does not say what is inside.It takes the whole row, like a paragraph. The heading and the text stay a heading and a text. The <div> only holds them together, for CSS for instance.
<span> is a word in the sentence. It does not say what it means.It does not start a new line. It marks “£12.90” and the rest of the line continues beside it. Use it to hang a class on, not if the word is important: that is what <strong> is for.
<div> wraps the card. The <span> wraps the price.Remove the CSS and the page still says the same things: mug, price. The two tags add no meaning. They only add a hook.
<div>. The second is an <h1>.Same words. Only the second is a heading: it enters the outline, and Google and screen readers can find it. A <div> does not become a heading even if you make it large with CSS.
Remove the CSS and the page reads exactly the same: they are containers with zero meaning.
Ask yourself: “if I removed this tag, would the user lose any information?” If the answer is no, and it only existed for the styling, <div> is the right choice. If the answer is yes, you need a tag that genuinely expresses that meaning.
The mistake to avoid: “divitis”
Because <div> always works and never errors, the temptation is to build the whole page out of it:
<div class="header">
<div class="nav">...</div>
</div>
<div class="main">
<div class="article">
<div class="title">How to write HTML</div>
<div class="text">...</div>
</div>
</div>On screen, with the right CSS, this page can look identical to a well-written one. But to anything without eyes it is a field of anonymous boxes: the browser does not know what the main content is, Google cannot tell where the article begins, and a screen reader can offer no shortcuts because there is nothing to announce. Note too that <div class="title"> is not a heading: it appears in no document outline, whereas an <h1> does.
<div> and <span> are the last resort, not the first. Look first for the tag that describes what you are writing; if, and only if, none exists, use a neutral container.
The next chapter gives you the tags to look for before falling back on <div>.
12. Semantic HTML: the page's structure
HTML5 provides a set of tags that behave exactly like a <div> — they are block containers — with one decisive difference: they declare the role of the part of the page they enclose.
The structural tags
| Tag | What it encloses | How many per page |
|---|---|---|
| <header> | The heading area: logo, title, menu. It can also sit inside an <article>. | One main one, several inner ones |
| <nav> | A block of important navigation links. | More than one (menu, breadcrumbs) |
| <main> | The content specific to this page, excluding whatever repeats everywhere. | Exactly one |
| <article> | Content that stands on its own: a post, a review, a product card. | As many as needed |
| <section> | A thematic part of the document, always introduced by a heading. | As many as needed |
| <aside> | Side content: a sidebar, a callout box, related articles. | As many as needed |
| <footer> | The closing area: copyright, contacts, legal links. Also valid inside an <article>. | One main one, several inner ones |
A complete page
Here is how they fit together. Compare this structure with the field of <div> elements from the previous chapter: the on-screen result can be the same, the information conveyed is not. Click a region of the page or a line of the code.
Interactive example
The page, region by region
Each box is a <div> that has learned to say who it is.
The Codedge blog
Home · Blog · Contact
How to write clean HTML
Content first, styling second.
Related articles
<header> is the heading area.Logo, site title, sometimes the menu. It can also sit inside an article. On screen it is a strip at the top: to the browser it says “the page starts here”.
<nav> is the menu, not just any list.Only the links you move around the site with. A shopping list stays a <ul>. A listener can ask: “take me to the menu”.
<main> is this page's own content.Exactly one. Inside it sit the article and, if needed, the side column. The menu and the copyright stay outside: they repeat on every page.
<article> stands on its own.A post, a review, a product card. If you lift it off the page and publish it elsewhere, it still makes sense. That is why it is not a <div>.
<aside> sits beside the point.Related articles, a callout box. Useful, but if you remove it the main article is still whole.
<footer> closes.Copyright, contacts, legal links. Like <header>, it can also sit at the end of a single article.
<article>, <section> or <div>?
This is the recurring question. Answer these three in order:
- Would this block make sense published on its own, detached from the rest of the page (in an RSS feed, shared on social media)? If yes →
<article>. - Otherwise: is it a thematic part with a heading of its own? If yes →
<section>. - Otherwise: it exists purely for the styling →
<div>.
<section> without a heading is almost always a <div>The <section> tag marks a section of the document's outline: if there is no <h2>–<h6> introducing it, the section has no name and you are conveying nothing. In that case use a <div>, which is more honest.
The concrete benefit: navigation shortcuts
<header>, <nav>, <main>, <aside> and <footer> are landmarks: reference points that screen readers list on request. A blind user can jump straight to the main content instead of listening to the menu again on every page of the site. With <div> elements that possibility simply does not exist.
The same principle applies to keyboard users. By making the first element of the <body> a link pointing at the <main>, you offer the same shortcut:
<a href="#content" class="skip-to-content">Skip to content</a>
...
<main id="content"> ... </main>The page is structured and readable. One last thing it cannot do: receive something from the user.
13. Forms: collecting data from the user
Until now the page speaks and the user listens. With forms the flow reverses: searches, logins, sign-ups, orders and comments all go through here.
The <form> container
<form action="/signup" method="post"> ... </form>action: the address the data is sent to.method: how it is sent.getappends it to the URL, leaving it visible in the address bar: fine for a search, which is then shareable and bookmarkable.postsends it in the request body, outside the URL: mandatory for passwords, personal data and anything that changes something on the server.
HTML builds the form and sends it. Receiving the data, checking it and storing it requires a program on the server (in PHP, Python, Node.js…) or some JavaScript. If you open a form from your own computer and press Submit, nothing useful will happen: that is normal.
Three things to understand before all the field types: type changes the field, the <label> gives it a clickable name, and name is what leaves for the server. The widget below shows them.
Interactive example
One field, three rules
On the right you can type and click. Nothing is sent: it is only to see how it works.
<input type="text"> <input type="email"> <input type="password">
<label for="email">Email</label> <input type="email" id="email">
<label for="with-name">With name</label> <input id="with-name" name="email"> <label for="without-name">Without name</label> <input id="without-name"> <button>Send</button>
<input placeholder="Email"> <label for="ok">Email</label> <input id="ok">
type changes.Type in the three fields. The first is text. The second wants an @. The third hides the letters. They are not three different tags: it is <input> with three attributes.
The cursor still goes in. for and id are the same name: that is how the label and the field become one thing. Without that link it is a mute box.
Only the field with name leaves. The other vanishes, with no error. id is for the label. name is for sending. You need both.
Type in the first field: “Email” disappears. In the second the word stays above, always. The placeholder is a format example, not the field's name.
The fields: <input> and its type
<input> is an empty tag that changes appearance and behaviour completely according to its type attribute:
| type | What it produces |
|---|---|
| text | A single-line text field. This is the default. |
| An email field: on a phone it shows the keyboard with the @ and the browser checks the format. | |
| password | Hides the characters as they are typed. |
| number | Numbers, with stepper arrows and min and max attributes. |
| tel | Phone numbers: brings up the numeric keypad. |
| date | A date picker supplied by the browser. |
| checkbox | A box with an independent choice (I accept the terms). |
| radio | A single choice among options: they exclude each other. |
| file | Selecting a file to upload. |
| submit | The button that submits the form. |
Choosing the right type is not pedantry: on its own it gives you the right keyboard on mobile, a validity check and, for dates, a calendar you did not have to program.
Every field needs a <label>
A field without a label is an empty box whose purpose nobody knows. The label is connected to the field by matching its for with the field's id:
<label for="email">Email address</label>
<input type="email" id="email" name="email">The connection has two effects: the screen reader announces “email address, text field” when the user reaches it, and clicking the label moves the cursor into the field, widening the clickable area — a real benefit on a touchscreen.
placeholder does not replace a labelThe grey text inside the field vanishes as soon as you start typing: anyone who gets distracted has no way of knowing what belonged there, and the contrast is often too low to read anyway. Use the placeholder only for an example of the format, and always include a visible <label>.
The name attribute: the most important of all
When the form is submitted, every field sends a name = value pair. The name comes from the name attribute: it is the key the server uses to find the data.
name, the field is not submittedNo error, no warning: the data simply never leaves. It is the number one cause of forms “that don't work”. id is for the label, name is for the submission: you need both.
The other fields
<textarea>: multi-line text. It is not an input, it has a closing tag, and the initial value is written between the two tags.<select>with<option>: a dropdown menu. Each option has avalueattribute, and that is what actually gets sent.<button>: the button. Inside a form the default type issubmit: if you need a button that sends nothing, writetype="button"explicitly.<fieldset>with<legend>: groups related fields and gives the group a name. Indispensable withradiobuttons, since otherwise it is unclear which question they answer.
Validation that needs no code
The browser already knows how to reject an incomplete or malformed form, without a line of JavaScript:
| Attribute | Constraint imposed |
|---|---|
| required | The field cannot be left empty. |
| minlength maxlength | Minimum and maximum text length. |
| min max | Minimum and maximum value for numbers and dates. |
| pattern | The text must match a pattern (a 5-digit postcode, say). |
| autocomplete | Lets the browser fill in name, email and address automatically. |
These checks improve the user's experience, but they must always be repeated on the server: anyone can bypass the browser's validation.
A complete form
<form action="/contact" method="post">
<label for="name">Full name</label>
<input type="text" id="name" name="name" required autocomplete="name">
<label for="email">Email</label>
<input type="email" id="email" name="email" required
placeholder="[email protected]" autocomplete="email">
<label for="reason">Reason for getting in touch</label>
<select id="reason" name="reason">
<option value="info">Request for information</option>
<option value="quote">Quote</option>
<option value="support">Support</option>
</select>
<fieldset>
<legend>How would you prefer to be contacted?</legend>
<input type="radio" id="by-email" name="contact" value="email" checked>
<label for="by-email">By email</label>
<input type="radio" id="by-phone" name="contact" value="phone">
<label for="by-phone">By phone</label>
</fieldset>
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required></textarea>
<input type="checkbox" id="privacy" name="privacy" required>
<label for="privacy">I have read the privacy policy</label>
<button type="submit">Send request</button>
</form>Look at the two radio inputs: they have different id values (which must be unique) but the same name. It is that shared name that makes them alternatives to each other: change it and they become two independent choices the user can select at the same time.
The <body> can now do everything. Let's return to the top of the file, where you decide how the page presents itself to the world.
14. The head: metadata, SEO and social previews
In chapter 3 we defined the <head> as the document's invisible part. It does not hold what you read on the page, but everything needed in order to read it: how to interpret the characters, how to behave on phones, which stylesheets to load, what to show on Google and in chat apps.
The three lines you cannot do without
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HTML course for beginners | Codedge</title>charset: declares the character encoding. Without it, accents and emoji turn into gibberish. Write it among the very first lines of the head.viewport: tells the phone to use the screen's real width. Without this line the phone pretends to be a desktop and shows the page shrunk down and unreadable.title: the browser tab's title, the bookmark's name and the blue title in search results. It is different from the<h1>, which lives in the body: keep it under 60 characters.
These lines do not appear in the body. Click a line of the head: the place it ends up lights up.
Interactive example
Three lines, three places
<title> goes on the tab. The description goes on Google. Open Graph goes in the chat.
It is not the <h1> in the body. It is the text at the top of the browser and, usually, the blue title on Google.
It does not decide rank. It decides whether anyone clicks. About 150 characters.
Without them, the chat shows only the address. The image needs a full URL, with https://.
Getting found: metadata for search engines
<meta name="description" content="A practical guide to HTML: structure, semantic tags, links, images and forms explained step by step.">
<link rel="canonical" href="https://codedge.it/tutorials/html-fundamentals/">description: the roughly 150-character summary that appears under the title in search results. It does not directly affect ranking, but it decides whether anyone clicks.canonical: gives the page's official address. If the same content is reachable from several URLs, it stops it being treated as duplicate.
The preview when a link is shared
When you paste an address into WhatsApp, LinkedIn or Slack and a card appears with a title, a description and an image, that data comes from the Open Graph metadata. If you do not write it, the preview will be a bare link or a random crop of the page.
<meta property="og:title" content="HTML course for beginners">
<meta property="og:description" content="Structure, semantics, links, images and forms explained step by step.">
<meta property="og:image" content="https://codedge.it/images/preview.jpg">
<meta property="og:url" content="https://codedge.it/tutorials/html-fundamentals/">
<meta property="og:type" content="article">og:image needs an absolute address, with https://: social platforms download the image from their own servers, and a relative path would mean nothing to them. The standard size is 1200×630 pixels.
Linking CSS and JavaScript
The head is also where you declare the external files the page needs:
<link rel="stylesheet" href="/css/style.css">
<script src="/js/app.js" defer></script>The stylesheet goes in the head because it is needed before the page is painted. For scripts, the defer attribute tells the browser to download them in parallel and run them only once the page is ready: you avoid blocking rendering and you make sure the elements you want to manipulate already exist.
The head is rounded out by the tab icon: <link rel="icon" href="/favicon.ico">.
A head you can copy
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page title | Site name</title>
<meta name="description" content="A summary of the page in about 150 characters.">
<link rel="canonical" href="https://example.com/page/">
<meta property="og:title" content="Page title">
<meta property="og:description" content="A summary of the page.">
<meta property="og:image" content="https://example.com/preview.jpg">
<meta property="og:url" content="https://example.com/page/">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="/css/style.css">
</head>You have now seen the whole path: from the file's skeleton to the content tags, from semantic structure to forms, right through to the metadata. Before moving on to practice, let's look at the mistakes worth avoiding from the outset.
15. The 5 most common semantic mistakes (and how to avoid them)
To close on a practical note, here is a round-up of the mistakes beginners make most often, and the advice for fixing them straight away.
The `<br>` tag is only for breaking a line within a piece of text (a poem, a postal address). If you want to space two paragraphs or two visual boxes apart, use the CSS `margin` property. Do not force layout with HTML.
An inline element such as `<span>` or `<strong>` should never contain block elements like `<div>` or `<h2>`. Always respect the browser's logical flow, so you do not break the rendering.
Using an `<h3>` purely because it is smaller by default than an `<h2>`, wrecking the document's hierarchy, is a bad mistake. Use the right tags in logical numeric order, and change their visual size with the CSS `font-size` property.
Leaving images without an `alt` attribute (or putting the file name in it, e.g. `alt="new_logo_final2.png"`) is bad for SEO and a nightmare for screen reader users. Write short, clear, meaningful descriptions.
Using `<blockquote>` to indent text, or `<address>` just to make words italic, is an anti-pattern. Tags describe what the data is, not how it looks.
16. Quick reference (cheat sheet)
Here are the essential semantic tags to remember for structuring content correctly:
| Tag | Semantic role | Nesting rules |
|---|---|---|
| <header> | The main heading area of the page or of a section. | Usually holds logos, <h1>-<h6> headings or navigation. |
| <nav> | A section containing navigation links. | Often wraps an ordered <ol> or unordered <ul> list. |
| <main> | The document's main and unique content. | There must be only one per page, and it must not sit inside other semantic tags. |
| <article> | Self-contained, independent content (a blog post, say). | It should have a heading of its own (<h2>-<h6>). |
| <section> | A generic thematic section of the document. | Use it only when the section is logically meaningful and carries a heading. |
17. Practical challenge: put yourself to the test
There is no better way to learn than getting your hands dirty. Try this small challenge in the playground or in your own editor before moving on to the quiz:
Build a semantic structure for a personal profile page (a CV), containing a heading area, a list of interests and an accessible contact form.
Step-by-step instructions:- Use a
<header>to enclose your name (in an<h1>) and a descriptive subtitle. - Create a
<section>with an<h2>heading about your interests, listing three of them in an unordered<ul>list. - Add a
<form>with an email input and a<textarea>for messages. - Correctly associate each input with a visible
<label>by pairing theforandidattributes.
18. Where to go next
You have laid the foundations of your house on the web. Now that you have the structure under control, you can start decorating it and making it smart. Here are the natural next stops on your learning path:
- CSS fundamentals: the logical next step, for adding colour and style and defining the spatial layout of your HTML tags.
- VS Code essentials: learn to write code quickly using Emmet shortcuts, automatic suggestions and the ideal working environment.
- Browsers and DevTools: find out how to inspect the DOM, explore a page's elements in real time and diagnose bugs.
- All tutorials: back to the full list, to pick your next topic.
19. Check what you have learned (quiz)
Test what you have learned in this guide with a quick 10-question multiple-choice quiz.