From frontend-design
Audit and optimize the semantic HTML of a website or web page. Use this skill when the user wants to improve accessibility, SEO, or code quality of their HTML — or says things like "improve the semantics", "fix the HTML structure", "make it accessible", "improve SEO markup", "fix the heading hierarchy", "replace divs with proper elements", "add ARIA labels", "fix form accessibility", "audit the HTML", or "make the markup more meaningful". Also use when the user shares HTML and asks if it's correct, well-structured, or standards-compliant. This skill produces structured violation reports before proposing any corrections, never improvising generic changes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend-design:html-semanticsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Ce skill guide des audits structurés de la sémantique HTML, dimension par dimension. L'objectif est de rendre chaque recommandation traçable jusqu'à une règle HTML5, WCAG, ou une spécification ARIA — jamais une improvisation générique.
Ce skill guide des audits structurés de la sémantique HTML, dimension par dimension. L'objectif est de rendre chaque recommandation traçable jusqu'à une règle HTML5, WCAG, ou une spécification ARIA — jamais une improvisation générique.
Le HTML sémantique sert trois maîtres simultanément :
<nav> dit plus qu'un <div class="nav">Un <div> est un élément de dernier recours. Chaque <div> ou <span> dans le code est une question : existe-t-il un élément HTML qui exprime mieux cette intention ?
Avant tout audit, établir ce contexte (demander à l'utilisateur si absent) :
## Contexte du site
- Type de site : [ex. blog / e-commerce / SaaS / portfolio / documentation / app web]
- Audience principale : [ex. grand public / développeurs / utilisateurs assistive tech]
- Priorité principale : [Accessibilité / SEO / Qualité de code / Les trois]
- Framework utilisé : [HTML pur / React / Vue / Jinja / autre]
- Pages concernées : [toutes / home / articles / formulaires / autre]
- État actuel estimé : [soupe de divs / partiellement sémantique / déjà structuré]
<div> et <span> suspectsAvant les audits ciblés, produire un inventaire des éléments génériques.
Rechercher dans le code tous les <div> et <span> et les classer :
## Inventaire des éléments génériques
| Élément | Classe/ID actuelle | Rôle probable | Élément cible |
|----------------------|---------------------------|------------------------|---------------|
| `<div class="nav">` | nav | Navigation principale | `<nav>` |
| `<div class="hero">` | hero | Section d'en-tête | `<header>` |
| `<div class="post">` | post | Article de blog | `<article>` |
| `<div onclick="...">` | btn-close | Bouton | `<button>` |
| `<span class="bold">` | bold | Emphase forte | `<strong>` |
| `<div class="aside">` | aside | Contenu secondaire | `<aside>` |
Cet inventaire oriente les audits suivants.
Effectuer chaque audit séparément. Rapport de violations d'abord, corrections ensuite.
Objectif : le document doit avoir une structure de landmarks complets et non-redondants, navigable sans CSS ni JavaScript.
Document
├── <html lang="fr"> ← attribut lang obligatoire
├── <head>
│ ├── <meta charset="UTF-8">
│ ├── <meta name="viewport">
│ └── <title> non vide, unique par page
└── <body>
├── <a href="#main"> skip link ← premier élément du body
├── <header> ← en-tête du site (une seule fois)
│ └── <nav aria-label="..."> ← navigation principale
├── <main id="main"> ← une seule fois, contenu principal
│ ├── <article> / <section>
│ └── <aside> (si applicable)
├── <nav aria-label="..."> ← nav secondaire (fil d'Ariane, etc.)
└── <footer> ← pied de page
## Rapport Structure et Landmarks
### Éléments manquants
- [ ] `<html>` sans attribut `lang` → WCAG 3.1.1 (A)
- [ ] Pas de `<main>` → lecteurs d'écran ne peuvent pas sauter au contenu
- [ ] Skip link absent (premier lien vers #main) → WCAG 2.4.1 (A)
- [ ] `<title>` vide ou générique ("Page") → HTML5 + SEO
### Mauvaises imbrications
- `<header>` à l'intérieur d'un `<div>` au lieu d'être enfant direct de `<body>`
- Deux `<main>` dans la même page (un seul autorisé)
- `<nav>` sans `aria-label` alors qu'il y en a plusieurs sur la page
### Landmarks redondants ou manquants
| Landmark attendu | Présent | Problème |
|----------------------|---------|---------|
| `<header>` | ✅ | |
| `<main>` | ❌ | Remplacé par `<div id="content">` |
| `<nav>` principal | ✅ | Pas de aria-label |
| `<footer>` | ✅ | |
| `<aside>` | ❌ | Sidebar non balisée |
<main> par page<nav> → chacun doit avoir un aria-label distinct<header> et <footer> peuvent apparaître plusieurs fois si dans <article> ou <section><section> doit avoir un titre accessible (h2–h6 ou aria-label)<article> = contenu autonome redistribuable (blog post, commentaire, widget)Objectif : un seul <h1>, progression sans sauts, titres descriptifs.
Extraire tous les titres dans l'ordre d'apparition dans le DOM :
## Rapport Hiérarchie des Titres
### Arbre des titres (ordre DOM)
h1: "Velnaris — Cité des Désirs"
h2: "Dernières publications"
h3: "100 Rencontres Urbaines"
h3: "Courtisanes de Velnaris"
h2: "À propos"
h4: "Contact" ← ⚠️ SAUT : h2 → h4, h3 manquant
### Violations
| Violation | Localisation | Règle |
|------------------------------------|---------------------|----------|
| Saut h2 → h4 (h3 manquant) | section #about | WCAG 1.3.1 |
| Titre h1 absent | page /shop | HTML5 |
| Deux h1 sur la même page | / (home) | HTML5 |
| Titre vide `<h2></h2>` | .sidebar | WCAG 2.4.6 |
| Titre utilisé pour styliser | `<h3 class="small">` | HTML5 |
### Règles
- Un seul `<h1>` par page, correspond au sujet principal
- Jamais sauter un niveau (h2→h4 interdit)
- Les titres doivent décrire le contenu, pas le style
- Un `<h2>` dans un `<aside>` est normal — la hiérarchie est par section
Objectif : tout élément cliquable est soit un <a> (navigation), soit un <button> (action). Rien d'autre.
| Intention | Élément correct | Anti-pattern fréquent |
|---|---|---|
| Naviguer vers une URL | <a href="..."> | <div onclick="location..."> |
| Déclencher une action | <button> | <div class="btn" onclick>, <a href="#"> |
| Activer/désactiver | <input type="checkbox"> ou <button role="switch"> | <div class="toggle"> |
## Rapport Éléments Interactifs
### Éléments interactifs non-sémantiques
| Code actuel | Problème | Correction |
|-------------------------------------|---------------------------------|------------|
| `<div class="btn" onclick="...">` | Non focusable clavier | `<button>` |
| `<a href="#">Fermer</a>` | Lien qui n'est pas une nav | `<button>` |
| `<span onclick="toggle()">` | Inaccessible entièrement | `<button>` |
| `<img onclick="openModal()">` | Pas de rôle ni de label | `<button><img alt="..."></button>` |
### Boutons sans libellé accessible
| Élément | Problème | Correction |
|----------------------------------|----------------------------|------------|
| `<button><svg>...</svg></button>` | SVG sans titre, bouton muet | `aria-label="Fermer"` |
| `<button class="close">×</button>` | "×" n'est pas un label | `aria-label="Fermer"` |
| `<a href="/page"><img src="logo.png"></a>` | Image sans alt | `alt="Accueil"` sur l'img |
### Liens sans href
`<a>` sans `href` n'est pas focusable et n'est pas un lien.
→ Si c'est une action : `<button>`
→ Si c'est un lien conditionnel : ajouter `href` ou `tabindex="0"` + `role="link"`
Objectif : chaque champ a un label associé, chaque groupe a un fieldset, les erreurs sont liées au champ.
<input>, <select>, <textarea>## Rapport Formulaires
### Associations label/input
| Input | Label présent | Association correcte | Problème |
|--------------------------------|---------------|----------------------|---------|
| `<input id="email">` | ✅ | `<label for="email">` | ✅ OK |
| `<input class="search">` | ❌ | — | ❌ Aucun label |
| `<input id="pwd">` | ✅ | Label visuellement proche mais sans `for` | ⚠️ |
| `<input placeholder="Nom">` | ❌ | Placeholder ≠ label | ❌ WCAG 1.3.1 |
### Groupes sans fieldset
- Groupe "Préférences de contact" (3 radios) → pas de `<fieldset><legend>`
- Groupe d'options → pas de `<fieldset><legend>`
### Attributs manquants
| Input | type manquant | required | autocomplete | aria-describedby |
|-------------------------|---------------|----------|--------------|------------------|
| Email | ✅ email | ❌ | ❌ | ❌ |
| Mot de passe | ✅ password | ❌ | ❌ (current-password) | ❌ |
| Téléphone | ✅ tel | ❌ | ❌ (tel) | ❌ |
### Gestion des erreurs
- Les messages d'erreur ne sont pas liés au champ via `aria-describedby`
- Les champs invalides n'ont pas `aria-invalid="true"`
- Les erreurs n'ont pas `role="alert"` ou `aria-live="polite"`
<div class="field">
<label for="email">
Adresse email
<span aria-hidden="true">*</span>
</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
autocomplete="email"
aria-describedby="email-hint email-error"
>
<p id="email-hint" class="hint">Format : [email protected]</p>
<p id="email-error" class="error" role="alert" hidden></p>
</div>
Objectif : utiliser les éléments de texte HTML5 selon leur signification, pas selon leur apparence visuelle.
| Élément | Signification | Anti-pattern fréquent |
|---|---|---|
<strong> | Importance forte (sémantique) | <b> pour styler en gras |
<em> | Emphase (sémantique) | <i> pour styler en italique |
<b> | Mise en valeur visuelle sans importance | <span style="font-weight:bold"> |
<i> | Terme technique, étranger, pensée | <span style="font-style:italic"> |
<time> | Date ou heure machine-readable | Date en texte simple |
<abbr> | Abréviation avec expansion | Acronyme sans explication |
<cite> | Titre d'œuvre (livre, film, article) | <em> ou <i> |
<q> | Citation courte inline | Guillemets manuels |
<blockquote> | Citation longue, avec cite URL | <p class="quote"> |
<code> | Code inline | <span class="code"> |
<pre><code> | Bloc de code | <div class="codeblock"> |
<mark> | Texte surligné/pertinent dans contexte | <span class="highlight"> |
<del> | Contenu supprimé | <span class="strikethrough"> |
<ins> | Contenu ajouté | <span class="underline"> |
<dfn> | Terme en cours de définition | <strong> pour premier usage |
<address> | Coordonnées de l'auteur/organisation | <p class="address"> |
<figure> | Contenu autonome avec légende | <div class="image-wrapper"> |
<figcaption> | Légende d'une figure | <p class="caption"> |
## Rapport Sémantique Textuelle
### Remplacements recommandés
| Code actuel | Problème | Correction |
|------------------------------------------|------------------------|------------|
| `<b>Important :</b>` | b ≠ importance forte | `<strong>` |
| `<i>L'Annihilation</i>` (titre de film) | i pour titre d'œuvre | `<cite>` |
| `Publié le 13 mars 2026` | Date non machine-readable | `<time datetime="2026-03-13">` |
| `<div class="quote">...</div>` | Citation non sémantique | `<blockquote>` |
| `<span class="code">npm install</span>` | Code non sémantique | `<code>` |
| `<div><img src="..."><p>Légende</p></div>` | Figure non structurée | `<figure><img><figcaption>` |
| `<p>ex.: <span>HTML</span> signifie...` | Terme non défini | `<dfn>HTML</dfn>` |
Objectif : toute image a un texte alternatif approprié à son rôle (informatif, décoratif, fonctionnel).
| Type | Règle alt | Exemple |
|---|---|---|
| Informative | Description concise du contenu | alt="Carte de Velnaris avec les districts" |
| Décorative | alt="" (vide, jamais absent) | Séparateurs, fonds, icônes purement ornementales |
| Fonctionnelle | Décrit l'action, pas l'image | alt="Rechercher" (pas alt="Loupe") |
## Rapport Images et Médias
### Attributs alt manquants ou incorrects
| Image | alt actuel | Problème | Correction |
|--------------------------------|-------------------|-----------------------|------------|
| `<img src="logo.png">` | absent | WCAG 1.1.1 — critique | `alt="Fantasy Vixens"` |
| `<img src="separator.svg">` | `alt="separator"` | Décorative → alt vide | `alt=""` |
| `<img src="search-icon.png">` | `alt="icon"` | Non descriptif | `alt="Rechercher"` |
| `<img src="hero.jpg">` | `alt="hero image"` | Redondant/inutile | Description du contenu de l'image |
### SVG inline sans accessibilité
\`\`\`html
<!-- Avant (muet) -->
<svg class="icon">...</svg>
<!-- Après (informatif) -->
<svg role="img" aria-label="Fermer le menu">
<title>Fermer le menu</title>
...
</svg>
<!-- Après (décoratif) -->
<svg aria-hidden="true" focusable="false">...</svg>
\`\`\`
### Vidéos et audio
- `<video>` sans `<track kind="captions">` → WCAG 1.2.2
- `<audio>` sans transcription liée → WCAG 1.2.1
- `autoplay` sans `muted` → WCAG 1.4.2
Complémentaire aux six audits, vérifier les signaux de structure pour les moteurs de recherche.
## Checklist SEO Sémantique
### Métadonnées essentielles
- [ ] `<title>` unique par page, 50–60 caractères
- [ ] `<meta name="description">` 120–160 caractères, présente sur chaque page
- [ ] `<link rel="canonical">` si contenu dupliqué possible
- [ ] `<html lang="fr">` (ou langue appropriée)
- [ ] `<meta name="viewport" content="width=device-width, initial-scale=1">`
### Open Graph (partage social)
- [ ] `og:title`
- [ ] `og:description`
- [ ] `og:image` (1200×630px recommandé)
- [ ] `og:url`
- [ ] `og:type` (website, article, product...)
### Structure données (Schema.org)
Selon le type de page :
| Type de page | Schema recommandé |
|-----------------|-------------------|
| Article de blog | `Article` ou `BlogPosting` |
| Produit | `Product` + `Offer` |
| Organisation | `Organization` |
| Personne | `Person` |
| FAQ | `FAQPage` |
| Fil d'Ariane | `BreadcrumbList` |
Exemple minimal pour un article :
\`\`\`html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Titre de l'article",
"datePublished": "2026-03-13",
"dateModified": "2026-03-13",
"author": { "@type": "Person", "name": "Auteur" },
"description": "Description courte"
}
</script>
\`\`\`
Ordre recommandé (de l'impact le plus large au plus ciblé) :
1. Audit A (Structure) → fondation, tout le reste s'y appuie
2. Audit B (Titres) → hiérarchie du contenu, essentiel pour SEO et AT
3. Audit C (Interactifs) → violations d'accessibilité critiques (WCAG A)
4. Audit D (Formulaires) → impact utilisateur direct, WCAG AA
5. Audit F (Images) → violations WCAG 1.1.1, souvent critiques
6. Audit E (Texte) → optimisation fine, rarement bloquant
7. Step 3 (SEO sémantique) → données structurées, métadonnées
Règle absolue : chaque remplacement d'élément doit :
## Checklist de validation sémantique
### Structure
- [ ] `<html lang="...">` présent et correct
- [ ] Un seul `<main>` par page
- [ ] Skip link présent comme premier lien
- [ ] Tous les `<nav>` multiples ont `aria-label`
- [ ] `<section>` ont tous un titre ou `aria-label`
### Titres
- [ ] Un seul `<h1>` par page
- [ ] Aucun saut de niveau (h1→h3 sans h2)
- [ ] Aucun titre utilisé pour son style visuel
### Interactivité
- [ ] Aucun `<div>` ou `<span>` avec `onclick` non corrigé
- [ ] Tous les boutons ont un label texte ou `aria-label`
- [ ] `<a>` ont tous un `href` réel (pas `#` comme seule valeur)
### Formulaires
- [ ] Tous les inputs ont un `<label>` associé via `for`/`id`
- [ ] Groupes de radios/checkboxes dans `<fieldset><legend>`
- [ ] Champs obligatoires marqués `required` + `aria-required`
- [ ] Erreurs liées via `aria-describedby`
### Images
- [ ] Toutes les `<img>` ont un attribut `alt` (même vide pour déco)
- [ ] SVG décoratifs ont `aria-hidden="true"`
- [ ] Images fonctionnelles décrivent l'action
### Texte
- [ ] `<strong>` utilisé pour importance, pas pour gras
- [ ] `<em>` utilisé pour emphase, pas pour italique
- [ ] Dates dans `<time datetime="...">`
- [ ] Code dans `<code>` ou `<pre><code>`
references/html5-elements.md — tableau complet des éléments HTML5 avec cas d'usagereferences/aria-guide.md — quand utiliser ARIA (et surtout quand ne pas l'utiliser)references/wcag-quick-ref.md — critères WCAG pertinents pour la sémantique HTML<button> > <div role="button">.<div class="nav"> par <nav class="nav"> ne change rien visuellement si le CSS cible la classe.references/aria-guide.md avant d'ajouter tout attribut ARIA.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub camauger/dev-skills --plugin frontend-design