Web Development: HTML, CSS and JavaScript
Exit

Lesson 2 of 17 · HTML and CSS

HTML: page structure, semantic tags, links and images

45 min Node.js 22 (Debian, git, npm, python3)

Objective

Write by hand an HTML page that is valid and well structured: the mandatory skeleton, headings and paragraphs, lists, the main semantic areas (header, nav, main, footer), links and images. At the end, you will have the home page and the "Nos chambres" (Our rooms) page of Riad Yasmine in Marrakech.

Key points

  • HTML describes the content and its meaning ("this is a heading", "this is a menu"), not its appearance: appearance is the job of CSS.
  • An element = opening tag + content + closing tag: <p>Bonjour</p>. Attributes give details about the element: <a href="chambres.html">.
  • Every page starts with <!doctype html>, then <html lang="fr">, a <head> (information about the page) and a <body> (what is displayed).
  • Semantic tags (header, nav, main, article, section, aside, footer) help screen readers and search engines.
  • Only one h1 per page, then h2, h3… in order, without skipping a level.
  • Every image has an alt attribute that describes what it shows.

1. Anatomy of an element

<a href="chambres.html" title="Voir les chambres">Nos chambres</a>
<!-- └ opening tag with two attributes ┘└ content ┘└ closing ┘ -->
  • Attributes are written name="value" in the opening tag, separated by spaces.
  • Elements nest like Russian dolls, and close in reverse order: <p>Un <strong>prix</strong> imbattable</p> is correct, <p>Un <strong>prix</p></strong> is not.
  • Some elements are void (no content, no closing tag): <img>, <br>, <meta>, <link>, <input>, <hr>.
  • <!-- ... --> is a comment: visible in the source code, never on the screen.
  • HTML ignores multiple spaces and line breaks: indent your code for yourself, not for the browser.

2. The page skeleton

<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Riad de charme dans la médina de Marrakech, six chambres, patio et terrasse.">
  <title>Riad Yasmine — Maison d'hôtes à Marrakech</title>
  <link rel="stylesheet" href="style.css">
  <link rel="icon" href="favicon.png">
</head>
<body>
  <!-- everything that is displayed -->
</body>
</html>
Line Role
<!doctype html> "this is modern HTML"; without it, the browser switches to compatibility (quirks) mode
lang="fr" the page language: screen reader pronunciation, translation, search ranking. For a page in Arabic: lang="ar" dir="rtl"
<meta charset="utf-8"> the encoding: without it, "Fès" and "مرحبا" show up as strange characters
viewport essential for correct display on a phone (lesson 1.6)
description the summary shown under the title in search results
<title> the text of the tab and of the link in Google: precise and unique for each page
<link rel="stylesheet"> links the CSS stylesheet

The <head> is not displayed: it contains metadata, information about the page.

3. Headings, paragraphs, emphasis

<h1>Riad Yasmine</h1>
<p>Une maison d'hôtes de six chambres, à cinq minutes de la place Jemaa el-Fna.</p>

<h2>Le riad</h2>
<p>Construit au <strong>XVIIIᵉ siècle</strong>, entièrement restauré en 2023.</p>

<h2>Les services</h2>
<h3>Petit-déjeuner</h3>
<p>Msemen, baghrir, amlou et jus d'orange pressé, <em>servis sur la terrasse</em>.</p>
<h3>Hammam</h3>
<p>Sur réservation, la veille.</p>
  • h1 to h6 form the outline of the page. A screen reader lets you jump from heading to heading: this is their real purpose. Never choose a heading for its size (size is set in CSS).
  • <p>: a paragraph. For a simple line break in an address, use <br>; to separate ideas, use a new <p>.
  • <strong>: importance (bold by default); <em>: emphasis (italic). These are meaning tags; <b> and <i> only carry a style.

4. Lists

<ul>                         <!-- bulleted list: the order does not matter -->
  <li>Wi-Fi gratuit</li>
  <li>Terrasse avec vue sur l'Atlas</li>
  <li>Navette depuis l'aéroport Marrakech-Ménara</li>
</ul>

<ol>                         <!-- numbered list: the order matters -->
  <li>Choisissez vos dates</li>
  <li>Choisissez votre chambre</li>
  <li>Confirmez par e-mail</li>
</ol>

<dl>                         <!-- description list: term / description -->
  <dt>Arrivée</dt><dd>à partir de 14 h</dd>
  <dt>Départ</dt><dd>avant 11 h</dd>
</dl>

A navigation menu is basically a list of links: many sites write it as <nav><ul><li><a …></a></li>…</ul></nav>.

5. Semantic structure tags

Before HTML5, everything was made of anonymous <div> elements. Today, we name the areas:

<body>
  <header>                       <!-- site header: logo, name, menu -->
    <h1>Riad Yasmine</h1>
    <nav>                        <!-- main navigation menu -->
      <a href="index.html">Accueil</a>
      <a href="chambres.html">Chambres</a>
      <a href="#contact">Contact</a>
    </nav>
  </header>

  <main>                         <!-- THE main content (only one per page) -->
    <section>                    <!-- a thematic part, with its heading -->
      <h2>Le riad</h2> …
    </section>
    <article>                    <!-- self-contained content: review, blog post -->
      <h2>« Un séjour inoubliable »</h2>
      <p>Accueil chaleureux, chambre impeccable. — Sophie, Lyon</p>
    </article>
    <aside>                      <!-- side content: box, related links -->
      <p>Offre : -15 % pour 5 nuits et plus.</p>
    </aside>
  </main>

  <footer>                       <!-- footer: address, legal notices -->
    <p>Derb Sidi Bouloukat, Marrakech</p>
  </footer>
</body>
Tag When to use it
header introduction of a page or a section
nav an important block of navigation links (not every link)
main the content specific to the page, only once
section a thematic part that has a heading
article content that would make sense on its own (a review, a product sheet)
aside secondary content
footer footer of a page or a section
div a container with no meaning, only for layout

Why it matters: a screen reader announces "navigation" and "main content" and lets the user go there directly; Google understands your page better; and your CSS is easier to read.

6. Links

<!-- Absolute link: to another site -->
<a href="https://www.visitmorocco.com/fr">Office national marocain du tourisme</a>

<!-- Relative links: to a page on the same site -->
<a href="chambres.html">Nos chambres</a>          <!-- same folder -->
<a href="chambres/suite-atlas.html">Suite Atlas</a> <!-- subfolder -->
<a href="../index.html">Accueil</a>               <!-- parent folder -->
<a href="/tarifs.html">Tarifs</a>                 <!-- from the site root -->

<!-- Anchor: to a location in the page -->
<a href="#contact">Nous contacter</a>
<section id="contact"> … </section>

<!-- Email -->
<a href="mailto:contact@riad-yasmine.ma">contact@riad-yasmine.ma</a>

<!-- Open in a new tab: keep this for external links -->
<a href="https://maps.example.com/riad" target="_blank" rel="noopener">Plan d'accès</a>

Quality rules:

  • the link text must say where it leads: "See our rooms" rather than "click here" (screen readers list links out of context);
  • prefer relative links inside your site: it will keep working locally, in testing and in production;
  • an id must be unique in the page.

7. Images

<img src="images/patio.jpg"
     alt="Le patio du riad et sa fontaine en zellige vert"
     width="800" height="533">
  • src: the path of the image (same rules as links);
  • alt: the alternative text, read by screen readers and displayed if the image does not load. Describe what the image brings. For a purely decorative image: alt="" (empty, but present);
  • width and height: the real dimensions in pixels. The browser reserves the space before loading, so the page does not "jump" (lesson 4.2).

Image with a caption:

<figure>
  <img src="images/terrasse.webp" alt="Terrasse au coucher du soleil, vue sur la Koutoubia" width="1200" height="800">
  <figcaption>La terrasse, où le thé est servi à 17 h.</figcaption>
</figure>

Formats to know: JPEG (photos), PNG (logos, transparency), WebP / AVIF (lighter photos, preferred), SVG (vector drawings, icons: sharp at any size).

8. Special characters and validation

Some characters have a meaning in HTML; to display them, you use entities:

Character Entity
< &lt;
> &gt;
& &amp;
" &quot;
non-breaking space (1 200 DH on a single line) &nbsp;

Thanks to utf-8, accents and Arabic are written directly, without entities.

To check a page, the W3C validator (validator.w3.org) reports badly closed tags, unknown attributes or duplicate id values. The browser, on the other hand, silently "repairs" errors… in its own way, which differs from one browser to another.

9. Organizing a site's files

riad/
├── index.html          ← the home page (name expected by web servers)
├── chambres.html
├── style.css
├── app.js
└── images/
    ├── patio.jpg
    └── terrasse.webp

Tips: file names in lowercase, without spaces or accents (suite-atlas.html, not Suite Atlas.html). A Linux server distinguishes Patio.jpg from patio.jpg, but Windows does not: this is the first cause of broken images after going live.

Common pitfalls

  • Forgetting <meta charset="utf-8">: accents display badly as soon as the site goes live.
  • Choosing a heading for its size (h4 because it is "smaller"): the page outline becomes inconsistent. Keep the logical order and set the size in CSS.
  • Images without alt or with alt="image": useless for a blind user and bad for search ranking.
  • Building everything with <div>: the page works, but it says nothing to screen readers and search engines.

Lab

You will build, step by step, two pages of Riad Yasmine in /root/riad:

  1. the skeleton of index.html;
  2. its semantic structure, with only one h1;
  3. the menu (relative links and anchor) and the contact section;
  4. an image with alternative text and dimensions;
  5. the chambres.html page with a list and a link back.

To write a file, the simplest way is cat > /root/riad/index.html <<'EOF', then the content, then a line EOF. Read your file again with cat.

Quiz

  1. Which three lines must appear in the <head> of every page?
  2. What is the difference between <section> and <div>?
  3. Write a link to the page tarifs.html located in the parent folder.
  4. What do you put in the alt attribute of a purely decorative image?
  5. Why should you specify width and height on an image?
Answers
  1. <meta charset="utf-8">, <meta name="viewport" content="width=device-width, initial-scale=1"> and <title>…</title>.
  2. <section> has a meaning (a thematic part, with a heading); <div> has none and only groups elements for layout.
  3. <a href="../tarifs.html">Nos tarifs</a>.
  4. An empty text, alt="": the attribute is present, and the screen reader ignores the image.
  5. So that the browser reserves the space for the image before it loads: the page does not shift while it is displayed.

Reference sources: MDN Web Docs in French (CC BY-SA 2.5 license). Original MasterLinux text.

Practical exercises

To do on Node.js 22 (Debian, git, npm, python3)

The online terminal is coming soon. Meanwhile, do these tasks on your own Linux machine, a virtual machine or WSL.

  1. Create /root/riad/index.html with the complete skeleton of a page: HTML5 doctype, an html element in French (lang="fr"), a head containing the utf-8 character set and a title that contains “Riad Yasmine”, then a body.
    Show a hintUse cat > /root/riad/index.html <<'EOF' … EOF, or printf with one line per argument.
    Show the solution
    printf '%s\n' '<!doctype html>' '<html lang="fr">' '<head>' '  <meta charset="utf-8">' '  <title>Riad Yasmine — Marrakech</title>' '</head>' '<body>' '  <p>Site en construction</p>' '</body>' '</html>' > /root/riad/index.html

    Check : grep -qi '<!doctype html>' /root/riad/index.html && grep -q '<html lang="fr"' /root/riad/index.html && grep -qi '<meta charset="utf-8"' /root/riad/index.html && grep -q '<title>[^<]*Riad Yasmine[^<]*</title>' /root/riad/index.html && grep -q '<body' /root/riad/index.html

  2. Structure the body of /root/riad/index.html with the semantic tags header, nav, main and footer. The page must contain ONLY ONE h1 heading (the name of the riad).
    Show a hintheader contains the h1; nav will hold the menu; main the main content; footer the address.
    Show the solution
    printf '%s\n' '<!doctype html>' '<html lang="fr">' '<head>' '  <meta charset="utf-8">' '  <title>Riad Yasmine — Marrakech</title>' '</head>' '<body>' '  <header>' '    <h1>Riad Yasmine</h1>' '    <nav></nav>' '  </header>' '  <main>' '    <p>Un riad de six chambres au cœur de la médina.</p>' '  </main>' '  <footer>' '    <p>Derb Sidi Bouloukat, Marrakech</p>' '  </footer>' '</body>' '</html>' > /root/riad/index.html

    Check : f=/root/riad/index.html; for b in header nav main footer; do grep -q "<$b" $f || exit 1; done; test "$(grep -o "<h1" $f | wc -l)" -eq 1

  3. In the nav, add a relative link to chambres.html and a link to the anchor #contact. In main, add a section whose identifier is contact (id="contact") with an h2 heading.
    Show a hint<a href="chambres.html">Chambres</a> and <section id="contact"><h2>Contact</h2>…</section>
    Show the solution
    printf '%s\n' '<!doctype html>' '<html lang="fr">' '<head>' '  <meta charset="utf-8">' '  <title>Riad Yasmine — Marrakech</title>' '</head>' '<body>' '  <header>' '    <h1>Riad Yasmine</h1>' '    <nav>' '      <a href="index.html">Accueil</a>' '      <a href="chambres.html">Chambres</a>' '      <a href="#contact">Contact</a>' '    </nav>' '  </header>' '  <main>' '    <p>Un riad de six chambres au cœur de la médina.</p>' '    <section id="contact">' '      <h2>Contact</h2>' '      <p>Écrivez-nous : <a href="mailto:contact@riad-yasmine.ma">contact@riad-yasmine.ma</a></p>' '    </section>' '  </main>' '  <footer>' '    <p>Derb Sidi Bouloukat, Marrakech</p>' '  </footer>' '</body>' '</html>' > /root/riad/index.html

    Check : f=/root/riad/index.html; grep -q 'href="chambres.html"' $f && grep -q 'href="#contact"' $f && grep -q 'id="contact"' $f && grep -q '<h2' $f

  4. In main, add the image images/patio.jpg with an alternative text (alt) that really describes the image, and its dimensions (width and height).
    Show a hint<img src="images/patio.jpg" alt="Le patio et sa fontaine en zellige" width="800" height="533">
    Show the solution
    sed -i 's#<p>Un riad de six chambres au cœur de la médina.</p>#&\n    <img src="images/patio.jpg" alt="Le patio du riad et sa fontaine en zellige vert" width="800" height="533">#' /root/riad/index.html

    Check : grep -o '<img[^>]*>' /root/riad/index.html | grep 'images/patio.jpg' | grep 'width=' | grep 'height=' | grep -Eq 'alt="[^"]+"'

  5. Create /root/riad/chambres.html (same skeleton, lang="fr"): an h1 “Nos chambres”, a bulleted list of at least three rooms and a link back to index.html.
    Show a hint<ul> <li>Suite Atlas</li> <li>Chambre Majorelle</li> <li>Chambre Ourika</li> </ul>
    Show the solution
    printf '%s\n' '<!doctype html>' '<html lang="fr">' '<head>' '  <meta charset="utf-8">' '  <title>Nos chambres — Riad Yasmine</title>' '</head>' '<body>' '  <h1>Nos chambres</h1>' '  <ul>' '    <li>Suite Atlas — 1 200 DH la nuit</li>' '    <li>Chambre Majorelle — 900 DH la nuit</li>' '    <li>Chambre Ourika — 750 DH la nuit</li>' '  </ul>' '  <p><a href="index.html">Retour à l’accueil</a></p>' '</body>' '</html>' > /root/riad/chambres.html

    Check : f=/root/riad/chambres.html; grep -q '<html lang="fr"' $f && grep -q '<h1>Nos chambres</h1>' $f && grep -q '<ul' $f && test "$(grep -o '<li' $f | wc -l)" -ge 3 && grep -q 'href="index.html"' $f

Official references consulted: Syntaxe de base du HTML — https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax ; Qu'y a-t-il dans l'en-tête ? Métadonnées d'une page Web — https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Structuring_content/Webpage_metadata ; Structurer des documents — https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Structuring_content/Structuring_documents ; Création d'hyperliens — https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Structuring_content/Creating_links ; Les images en HTML — https://developer.mozilla.org/fr/docs/Learn_web_development/Core/Structuring_content/HTML_images. Original MasterLinux text.