Esc
 Naviguer  Ouvrir Esc Fermer
Aller au contenu

5.1 — HTML sémantique

Débutant 30 min prérequis : axe 2 (web) lu

🎯 Objectif : ne plus écrire <div> partout. Utiliser les balises HTML5 qui décrivent la structure d’une page — tu obtiens accessibilité et SEO gratuitement, et le code reste lisible 2 ans plus tard.

À l'issue de cet axe, tu sauras :

  • Structurer une page avec header/nav/main/section/article/aside/footer
  • Construire un formulaire avec validation native et labels associés
  • Servir des images responsive avec srcset, sizes, picture
  • Connaître les attributs ARIA utiles (et savoir quand on n'en a PAS besoin)
  • Optimiser le SEO avec meta, OpenGraph, données structurées

First rule of ARIA : don’t use ARIA.

Avant d’ajouter role="button" sur un <div>, utilise simplement <button>. La balise native te donne :

  • Le bon rôle ARIA (sans l’écrire).
  • Le focus clavier (Tab).
  • L’activation par Espace et Entrée.
  • L’état désactivé (disabled).

90 % des problèmes d’accessibilité disparaissent juste en utilisant les bonnes balises.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mon Site — Page d'accueil</title>
<meta name="description" content="Description de 150 caractères max, c'est ce qui apparaît dans Google." />
<!-- OpenGraph (Twitter, Facebook, Slack…) -->
<meta property="og:title" content="Mon Site" />
<meta property="og:description" content="Description" />
<meta property="og:image" content="https://example.com/og-image.png" />
<meta property="og:url" content="https://example.com" />
<meta property="og:type" content="website" />
<!-- Favicons -->
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="canonical" href="https://example.com/" />
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header>...</header>
<nav>...</nav>
<main>...</main>
<footer>...</footer>
</body>
</html>
BaliseRôle
<meta charset="UTF-8">Toujours en 1er dans le head — assure le bon décodage
<meta name="viewport">Mobile-friendly (sans, le mobile zoome de force)
<title>Titre de l’onglet, affiché dans Google (50–60 caractères max)
<meta name="description">Description Google (150 caractères)
<link rel="canonical">URL canonique pour éviter le duplicate content
<body>
<header>
<a href="/">Logo</a>
<nav aria-label="Principal">
<ul>
<li><a href="/products">Produits</a></li>
<li><a href="/about">À propos</a></li>
</ul>
</nav>
</header>
<main>
<h1>Titre de la page</h1>
<article>
<h2>Article 1</h2>
<p>Contenu autonome — réutilisable hors contexte.</p>
</article>
<section aria-labelledby="témoignages">
<h2 id="témoignages">Témoignages</h2>
<!-- contenu d'un thème particulier -->
</section>
<aside>
<h2>Ailleurs sur le site</h2>
<!-- contenu connexe mais distinct -->
</aside>
</main>
<footer>
<p>&copy; 2026 Mon Site</p>
<nav aria-label="Pied de page">...</nav>
</footer>
</body>
BaliseQuand
<header>En-tête d’une page ou d’une section
<nav>Bloc de navigation principal (pas tous les groupes de liens)
<main>Une seule par page — contenu principal unique
<article>Contenu autonome : article de blog, post, fiche produit, commentaire
<section>Regroupement thématique avec un titre (<h2> à l’intérieur)
<aside>Contenu connexe mais séparé : barre latérale, encart
<footer>Pied d’une page ou d’une section
<div>Quand aucune autre balise sémantique ne convient
<h1>Une seule par page (le sujet principal)</h1>
<h2>Section</h2>
<h3>Sous-section</h3>
<h3>Sous-section</h3>
<h2>Section</h2>
<h3>Sous-section</h3>

Règles :

  • Un seul <h1> par page.
  • Pas de saut (<h2> puis <h4> direct = mauvais).
  • Le niveau indique la hiérarchie, pas la taille visuelle (CSS pour ça).
<form action="/submit" method="post">
<div>
<label for="email">Adresse e-mail</label>
<input
type="email"
id="email"
name="email"
required
autocomplete="email"
aria-describedby="email-help"
/>
<p id="email-help">On ne partage jamais ton e-mail.</p>
</div>
<div>
<label for="password">Mot de passe</label>
<input
type="password"
id="password"
name="password"
required
minlength="8"
autocomplete="current-password"
/>
</div>
<button type="submit">Se connecter</button>
</form>

Lier label et input permet :

  • Cliquer le label → le champ est focus.
  • Lecteurs d’écran annoncent le label.
  • Conformité WCAG.

Alternative : envelopper l’input dans le label (<label>Email <input ...></label>).

TypeBénéfice
emailClavier @ sur mobile, validation native
telClavier numérique sur mobile
urlClavier avec . et /
numberSpinner, validation numérique
date, time, datetime-localSélecteur natif
searchCroix pour effacer, comportement de recherche
passwordMasqué
colorPicker de couleur natif
rangeSlider
<input autocomplete="email" />
<input autocomplete="given-name" />
<input autocomplete="family-name" />
<input autocomplete="street-address" />
<input autocomplete="cc-number" />

Tu permets aux gestionnaires de mots de passe et au remplissage auto navigateur de fonctionner. C’est gratuit, énorme amélioration UX.

<input type="email" required />
<input type="text" pattern="[A-Z]{2}\d{4}" required title="Format : 2 lettres + 4 chiffres" />
<input type="number" min="1" max="100" step="0.5" />
<textarea minlength="20" maxlength="500"></textarea>

Le navigateur valide avant l’envoi. Tu peux personnaliser les messages avec JS, mais commence par les attributs natifs.

<fieldset>
<legend>Adresse de livraison</legend>
<label for="street">Rue</label>
<input id="street" name="street" />
<label for="city">Ville</label>
<input id="city" name="city" />
</fieldset>

Les lecteurs d’écran annoncent « Adresse de livraison, début du groupe » puis annoncent chaque champ avec ce contexte.

srcset et sizes — l’image adaptée à la taille d’écran

Section intitulée « srcset et sizes — l’image adaptée à la taille d’écran »
<img
src="/photo-800w.jpg"
srcset="
/photo-400w.jpg 400w,
/photo-800w.jpg 800w,
/photo-1600w.jpg 1600w
"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Description de l'image"
loading="lazy"
width="800"
height="600"
/>

Le navigateur choisit la version qui correspond à la résolution + densité de pixels de l’utilisateur. Téléphone classique → 400w. iPad Retina → 1600w.

<picture>
<source srcset="/photo.avif" type="image/avif" />
<source srcset="/photo.webp" type="image/webp" />
<img src="/photo.jpg" alt="..." width="800" height="600" />
</picture>

AVIF est ~40 % plus léger que JPEG, WebP ~30 %. Le <img> final est le fallback universel.

  • loading="lazy" : ne charge l’image que quand elle approche de la zone visible.
  • Toujours spécifier width et height (en attributs HTML) → le navigateur réserve l’espace, évite les sauts de layout (CLS).
Casalt
Image informativeDécrire ce qu’elle apporte (alt="Marie souriant à l'école")
Image décorativealt="" (le lecteur d’écran l’ignore)
Image dans un lienDécrire la destination (alt="Aller à la page produit")
Image avec texte par-dessusMettre le texte dans alt

Mauvais : alt="image", alt="photo.jpg", alt="img1". Ne mets jamais ça.

<video controls width="800" poster="/preview.jpg">
<source src="/video.webm" type="video/webm" />
<source src="/video.mp4" type="video/mp4" />
<track kind="subtitles" src="/subs-fr.vtt" srclang="fr" label="Français" default />
<track kind="subtitles" src="/subs-en.vtt" srclang="en" label="English" />
<p>Ton navigateur ne supporte pas la vidéo. <a href="/video.mp4">Télécharger</a>.</p>
</video>
  • <source> permet plusieurs formats (le navigateur choisit).
  • <track> ajoute des sous-titres (en .vtt) — obligatoire pour l’accessibilité.
  • Texte de fallback dans le tag pour les très vieux navigateurs.
  • Si tu utilises un <button>, n’ajoute pas role="button".
  • Si tu utilises un <nav>, n’ajoute pas role="navigation".
  • Si l’image est décorative et a alt="", n’ajoute pas aria-hidden="true".
<!-- Distinguer plusieurs nav -->
<nav aria-label="Principal">...</nav>
<nav aria-label="Secondaire">...</nav>
<!-- Étiqueter un bouton sans texte (icône seule) -->
<button aria-label="Fermer">
<svg><!-- icône X --></svg>
</button>
<!-- Lier une description à un input -->
<input id="pwd" aria-describedby="pwd-help" />
<p id="pwd-help">8 caractères minimum, dont 1 majuscule</p>
<!-- Annoncer dynamiquement (live regions) -->
<div role="status" aria-live="polite">
Panier mis à jour
</div>
<!-- Cacher visuellement mais garder pour les lecteurs d'écran -->
<span class="sr-only">Ouvrir le menu</span>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

Permet aux moteurs de recherche d’afficher des résultats enrichis (étoiles, recettes, événements…) :

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Comment apprendre HTML",
"author": {
"@type": "Person",
"name": "Marie Dupont"
},
"datePublished": "2026-01-15",
"image": "https://example.com/hero.jpg"
}
</script>

Catalogue : schema.org. Tester avec Rich Results Test.

/robots.txt
User-agent: *
Allow: /
Disallow: /admin/
Sitemap: https://example.com/sitemap.xml
/sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2026-04-29</lastmod>
</url>
<url>
<loc>https://example.com/blog</loc>
<lastmod>2026-04-01</lastmod>
</url>
</urlset>

La plupart des frameworks (Next.js, Astro, etc.) génèrent ça automatiquement.

Tu veux un bouton qui ouvre un modal. Quelle balise utilises-tu ?
Tu mets une image décorative (juste un fond visuel sans info). Quel alt ?
Tu construis un site marketing avec 4 sections : Hero, Caractéristiques, Témoignages, Tarifs. Quel sectionnement HTML ?
  • MDN — Structurer le web avec HTMLdeveloper.mozilla.org
  • HTML5 Doctor — quelle balise pour quoi
  • Web Accessibility Initiative — ARIA Authoring Practicesw3.org/WAI/ARIA/apg
  • Schema.org — pour les données structurées
  • Lighthouse (intégré à Chrome DevTools) — auditer SEO + a11y

Suite : 5.2 — CSS moderne pour donner vie à ce HTML proprement structuré.