` partout.
```html
Accueil
```
```html
Envoyer
Envoyer
```
Les éléments sémantiques apportent gratuitement : accessibilité clavier, rôles ARIA implicites, compatibilité lecteur d'écran.
---
## Section 5 : Checklist de test
### Devices à tester
| Device | Largeur | Priorité |
|--------|---------|----------|
| iPhone SE (2020/2022) | 375px | Haute — écran le plus étroit mainstream |
| iPhone 14 / 15 | 390px | Haute — référence iOS courante |
| Android mid-range (Pixel 6a, Samsung A54) | 360–412px | Haute |
| iPad / iPad Mini | 768px | Moyenne |
| Desktop laptop | 1280–1440px | Baseline |
### Modes à tester
- [ ] **Portrait** (défaut)
- [ ] **Paysage (landscape)** — vérifier que rien ne déborde, les éléments fixes ne prennent pas trop de hauteur
- [ ] **Dark mode** — activer dans les réglages du device ou de l'OS
- [ ] **Reduced motion** — activer "Réduire les animations" dans l'accessibilité OS
- [ ] **Zoom texte à 150%** — dans les réglages d'accessibilité du navigateur
### Lighthouse — cibles par métrique
Lancer en mode "Mobile" :
| Score | Cible |
|-------|-------|
| Performance | ≥ 80 |
| Accessibility | ≥ 90 |
| Best Practices | ≥ 90 |
| SEO | ≥ 90 |
Points d'accessibilité à corriger en priorité :
- Contraste insuffisant (auto-détecté)
- Images sans `alt`
- Éléments interactifs sans label
- Liens avec un texte générique ("cliquez ici", "en savoir plus")
- Tap targets trop petits
### Vérification lecteur d'écran (bases)
Sur iOS : activer **VoiceOver** (triple-clic bouton latéral).
Sur Android : activer **TalkBack**.
Parcourir la page avec les gestes de base :
- Le titre principal est-il annoncé correctement ?
- Les boutons ont-ils un libellé clair ?
- Les images décoratives ont-elles `alt=""`?
- Les champs de formulaire ont-ils des labels associés ?
### Checklist finale avant livraison
- [ ] Aucun scroll horizontal sur aucun device testé
- [ ] Tous les éléments interactifs accessibles au clavier (Tab + Enter/Space)
- [ ] focus-visible visible et distinctif
- [ ] Tous les inputs avec font-size ≥ 16px
- [ ] Textes avec contraste ≥ 4.5:1 (normal) ou 3:1 (grand)
- [ ] Images avec `alt` descriptif ou `alt=""` si décoratif
- [ ] Formulaires avec `
` associé à chaque input
- [ ] Dark mode : texte lisible, pas de blanc éblouissant
- [ ] Reduced motion : aucune animation qui se joue quand même
- [ ] Lighthouse Accessibility ≥ 90
- [ ] Pas de zoom automatique iOS sur les inputs
- [ ] Touch targets ≥ 44×44px
---
## Section 6 : Pièges iOS et Android
### 100vh sur Safari — le piège classique
```css
/* ❌ 100vh inclut la barre d'adresse Safari → contenu coupé */
.hero { height: 100vh; }
/* ✅ Utiliser svh/dvh (Baseline 2023) */
.hero {
height: 100vh; /* fallback navigateurs anciens */
height: 100svh; /* small viewport height — le plus sûr */
}
/* dvh = dynamique, s'adapte quand les barres apparaissent/disparaissent */
.fullscreen-overlay {
height: 100dvh;
}
```
### -webkit-tap-highlight-color — le flash bleu au tap
```css
/* Supprimer le highlight par défaut sur mobile WebKit */
* {
-webkit-tap-highlight-color: transparent;
}
/* Restaurer un feedback visible pour l'accessibilité */
button:active, a:active {
opacity: 0.7;
}
```
### touch-action — contrôle des gestes
```css
/* Empêcher le double-tap zoom sur les boutons (garde le pinch-zoom) */
.btn, a {
touch-action: manipulation;
}
/* Zone de carte/canvas : gérer les gestes manuellement */
.map-container {
touch-action: none;
}
/* Scroll vertical uniquement (ex: carousel horizontal géré en JS) */
.vertical-only {
touch-action: pan-y;
}
```
### Rubber-banding iOS et scroll des modals
```css
/* Empêcher le scroll de traverser un modal */
.modal-open body {
overflow: hidden;
position: fixed; /* corrige le bleed-through iOS */
width: 100%;
}
/* Contenir le scroll dans un élément sans propager au parent */
.modal-body,
.dropdown-menu,
.sidebar-scroll {
overscroll-behavior: contain;
}
/* Désactiver le pull-to-refresh (app-like) */
html {
overscroll-behavior-y: none;
}
```
### iOS input appearance — reset complet
```css
/* Empêcher le style natif iOS de surcharger le vôtre */
input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="number"],
input[type="search"],
input[type="date"],
input[type="time"],
textarea,
select {
-webkit-appearance: none;
appearance: none;
}
```
### Android text-size-adjust
```css
/* Chrome Android gonfle le texte dans les conteneurs larges (> 217 chars) */
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
```
---
## Section 7 : Font loading et images — performance CLS/LCP
### Stratégie font-display
```css
/* ❌ Par défaut (auto) : texte invisible jusqu'à 3s (FOIT) */
/* ✅ swap : affiche le fallback immédiatement, swap quand chargé */
@font-face {
font-family: "MyFont";
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap;
font-weight: 400;
font-style: normal;
}
/* Pour les polices décoratives non-critiques */
@font-face {
font-family: "DisplayFont";
src: url('/fonts/display.woff2') format('woff2');
font-display: optional; /* navigateur peut ignorer sur connexion lente */
}
```
### Réduire le CLS avec size-adjust sur le fallback
```css
/* Aligner les métriques du fallback sur la police web pour éviter le reflow */
@font-face {
font-family: "MyFont-fallback";
src: local("Arial");
size-adjust: 105%;
ascent-override: 90%;
descent-override: 25%;
line-gap-override: 0%;
}
body {
font-family: "MyFont", "MyFont-fallback", sans-serif;
}
```
### WOFF2 uniquement
```css
/* En 2025 : seul WOFF2 nécessaire — support > 95%, 30% mieux compressé */
@font-face {
src: url('/fonts/myfont.woff2') format('woff2');
/* Plus besoin de woff, ttf, ou eot */
}
```
### Preload et preconnect
```html
```
### Images — width/height obligatoires
```html
```
### fetchpriority — indiquer l'image LCP
```html
```
### srcset et picture — servir la bonne résolution
```html
```
---
## Section 8 : Scroll et navigation mobile
### scrollbar-gutter — éviter le CLS de la scrollbar
```css
/* Réserver l'espace de la scrollbar même quand elle n'est pas visible */
html {
scrollbar-gutter: stable;
}
```
### scroll-behavior — smooth scroll accessible
```css
/* Appliquer uniquement si l'utilisateur n'a pas demandé reduced motion */
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
```
### scroll-snap — carousels CSS natifs
```css
/* Carousel horizontal */
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding: 0 16px;
-webkit-overflow-scrolling: touch;
}
.carousel-item {
flex-shrink: 0;
scroll-snap-align: start;
width: 80%;
}
/* Sections plein écran — 'proximity' plutôt que 'mandatory' pour ne pas piéger */
.sections-wrapper {
height: 100svh;
overflow-y: auto;
scroll-snap-type: y proximity;
}
.section-fullpage {
height: 100svh;
scroll-snap-align: start;
}
```
---
## Section 9 : CSS moderne (Baseline 2024-2025)
### Container queries — media queries pour composants
```css
/* Le parent déclare qu'il est un container */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Le composant se style selon la largeur de SON container */
@container card (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}
/* ⚠️ Un container ne peut PAS se styler lui-même */
```
### text-wrap: balance et pretty
```css
/* Équilibrer la longueur des lignes dans les titres */
h1, h2, h3 {
text-wrap: balance;
}
/* Éviter les mots orphelins dans le corps de texte */
p {
text-wrap: pretty;
}
```
### CSS nesting natif (Baseline 2024)
```css
/* Plus besoin de préprocesseur */
.card {
padding: 16px;
& .card__title {
font-size: 1.25rem;
}
&:hover {
box-shadow: var(--shadow-md);
}
@media (min-width: 768px) {
padding: 24px;
}
}
```
### light-dark() — dark mode simplifié
```css
:root {
color-scheme: light dark;
}
.card {
background: light-dark(#ffffff, #1e293b);
color: light-dark(#1a1a1a, #e2e8f0);
border-color: light-dark(#e5e7eb, #374151);
}
```
### color-mix() — dériver des variantes d'une couleur
```css
:root {
--color-primary: #2563eb;
--color-primary-light: color-mix(in srgb, var(--color-primary) 20%, white);
--color-primary-dark: color-mix(in srgb, var(--color-primary) 80%, black);
}
```
### @layer — contrôle de la cascade
```css
/* Déclarer l'ordre : les layers bas ont moins de priorité */
@layer reset, base, components, utilities;
@layer reset {
* { box-sizing: border-box; margin: 0; }
}
/* Utile pour isoler du CSS tiers dans un layer faible */
@layer vendor {
@import url("third-party.css");
}
/* Les styles hors-layer gagnent toujours sur les styles en layer */
```
### View transitions (Baseline 2025)
```css
/* Toujours conditionner à prefers-reduced-motion */
@media (prefers-reduced-motion: no-preference) {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
}
}
/* Nommer des éléments pour des transitions ciblées */
.hero-image {
view-transition-name: hero;
}
/* Cross-document (MPA) */
@view-transition {
navigation: auto;
}
```
---
## Section 10 : Performance CSS avancée
### content-visibility: auto — skip du rendu hors-écran
```css
/* Accélère le rendu initial des pages longues */
.section-below-fold {
content-visibility: auto;
contain-intrinsic-block-size: auto 500px; /* OBLIGATOIRE : réserver l'espace */
}
/* ⚠️ Safari : find-in-page ne trouve pas le contenu caché */
/* ⚠️ Ne pas utiliser content-visibility: hidden — retire de l'arbre d'accessibilité */
```
### CSS containment — isoler les sous-arbres coûteux
```css
/* contain: content = layout + paint + style */
.widget {
contain: content;
}
/* Utile pour les listes dynamiques */
.feed-item {
contain: layout paint;
}
```
### will-change — avec parcimonie
```css
/* ❌ JAMAIS global — explose la mémoire GPU sur mobile */
* { will-change: transform; }
/* ✅ Uniquement sur les éléments qui animent en boucle */
.spinner {
will-change: transform;
}
/* Idéalement : ajouter en JS avant l'animation, retirer après */
```
### Animer uniquement transform et opacity
```css
/* ❌ Déclenche un reflow à chaque frame */
.card:hover {
width: 110%;
margin-top: -5px;
}
/* ✅ transform et opacity sont composités GPU — jamais de reflow */
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: scale(1.02) translateY(-4px);
}
```
---
## Section 11 : Accessibilité avancée
### forced-colors (Windows High Contrast Mode)
```css
@media (forced-colors: active) {
/* Restaurer les bordures sur les éléments qui utilisent background-color */
.card {
border: 1px solid ButtonText;
}
/* Garder le contrôle sur les éléments custom */
.custom-checkbox {
forced-color-adjust: none;
}
}
```
### prefers-contrast
```css
@media (prefers-contrast: more) {
:root {
--color-border: #000000;
--color-text-muted: #444444;
}
.btn-ghost {
border-width: 2px;
}
}
```
### focus-visible — utiliser outline, pas box-shadow
```css
/* ❌ box-shadow invisible en Windows High Contrast */
:focus-visible {
box-shadow: 0 0 0 2px var(--color-primary);
}
/* ✅ outline survit au mode forced-colors */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Supprimer l'outline uniquement pour les clics souris */
:focus:not(:focus-visible) {
outline: none;
}
```
---
## Section 12 : Print styles
```css
@media print {
nav, .sidebar, .cookie-banner, .modal, footer, .btn, .social-links {
display: none !important;
}
a[href]::after {
content: " (" attr(href) ")";
font-size: 0.875em;
}
img, pre, blockquote, table {
break-inside: avoid;
}
h1, h2, h3 {
break-after: avoid;
}
body {
font-family: Georgia, serif;
font-size: 12pt;
color: #000;
background: #fff;
}
.container {
max-width: 100%;
padding: 0;
}
}
```
---
## Section 13 : Checklist complète mise à jour
### Devices à tester
| Device | Largeur | Priorité |
|--------|---------|----------|
| iPhone SE (2020/2022) | 375px | Haute — écran le plus étroit mainstream |
| iPhone 14 / 15 | 390px | Haute — référence iOS courante |
| Android mid-range (Pixel 6a, Samsung A54) | 360–412px | Haute |
| iPad / iPad Mini | 768px | Moyenne |
| Desktop laptop | 1280–1440px | Baseline |
### Modes à tester
- [ ] Portrait
- [ ] Paysage
- [ ] Dark mode
- [ ] Reduced motion
- [ ] Zoom texte à 150%
- [ ] Contrast élevé (forced-colors)
### Core Web Vitals — cibles
| Métrique | Cible | Impact |
|----------|-------|--------|
| LCP (Largest Contentful Paint) | < 2.5s | Image hero, fonts |
| CLS (Cumulative Layout Shift) | < 0.1 | width/height sur img, font size-adjust |
| INP (Interaction to Next Paint) | < 200ms | Animations GPU-only, pas de reflow |
### Lighthouse — cibles
| Score | Cible |
|-------|-------|
| Performance | ≥ 80 |
| Accessibility | ≥ 90 |
| Best Practices | ≥ 90 |
| SEO | ≥ 90 |
### Checklist finale
- [ ] Aucun scroll horizontal sur aucun device testé
- [ ] Tous les éléments interactifs accessibles au clavier (Tab + Enter/Space)
- [ ] focus-visible visible et utilise outline (pas box-shadow)
- [ ] Tous les inputs avec font-size ≥ 16px
- [ ] Textes avec contraste ≥ 4.5:1 (normal) ou 3:1 (grand)
- [ ] Images avec `alt` descriptif ou `alt=""` si décoratif
- [ ] Images avec `width` et `height` déclarés (CLS)
- [ ] Image LCP avec `fetchpriority="high"` et `loading="eager"`
- [ ] Formulaires avec `` associé à chaque input
- [ ] Pas de `height: 100vh` sans fallback `100svh`
- [ ] `overscroll-behavior: contain` sur les overlays scrollables
- [ ] `-webkit-tap-highlight-color: transparent` appliqué
- [ ] `touch-action: manipulation` sur les boutons
- [ ] `font-display: swap` sur toutes les @font-face
- [ ] Polices web en WOFF2 uniquement
- [ ] Polices critiques preload dans le ``
- [ ] Dark mode fonctionne (si implémenté)
- [ ] Animations respectent `prefers-reduced-motion`
- [ ] Print styles testés
- [ ] Lighthouse Accessibility ≥ 90
- [ ] LCP < 2.5s, CLS < 0.1, INP < 200ms
---
*Last updated: 2025-03 — Revoir si : nouvelles directives WCAG (3.0), changements des Core Web Vitals Google, ou nouveaux appareils avec safe areas non standards.*