Mise en page de documentation sans framework : barre latérale sticky, contenu, table des matières à droite avec CSS Grid

Cet article vous a aidé ?
Docs CSS Grid orientés ops
Pas de framework. Aucune excuse.

Mise en page de documentation sans framework : barre latérale sticky, contenu, table des matières à droite avec CSS Grid

Votre page de docs a l’air correcte sur une capture d’écran. Puis elle arrive en production : une barre latérale sticky qui ne colle plus, un TOC qui tremble, et une colonne de contenu qui s’étire comme du caramel ou se comprime en un texte hétérogène.

Ceci est le guide de terrain pour construire une mise en page de documentation en trois colonnes avec CSS Grid — navigation à gauche, contenu central, TOC à droite — sans framework et sans la spirale habituelle « pourquoi sticky me déteste ».

Pourquoi cette mise en page casse en production

Les pages de documentation à trois colonnes échouent pour des raisons ennuyeuses, ce qui explique pourquoi elles échouent si souvent. Pas parce que CSS Grid ne peut pas le faire — Grid le fait très bien — mais parce que la page autour de la grille (headers, conteneurs de scroll, règles d’overflow, bannières injectées, prompts cookie et wrappers « bien intentionnés ») change discrètement les règles dont dépend le positionnement sticky.

Quand un SRE dit « ça marche en staging », il veut généralement dire « ça marche sur mon portable avec un seul viewport et sans scripts tiers ». Les pages de docs sont pires : pixels marketing, widgets de feedback et surligneurs de syntaxe se joignent à la fête. Chacun d’eux peut introduire un conteneur de scroll ou forcer un reflow au scroll. Voilà comment apparaissent les symptômes classiques :

  • Barre de navigation sticky qui cesse de coller à mi-chemin.
  • TOC droit qui chevauche le pied de page ou disparaît derrière.
  • Colonne de contenu qui déborde horizontalement à cause de longues lignes de code.
  • Scroll saccadé quand un script recalcule l’état actif du TOC trop souvent.

L’approche correcte est de traiter la mise en page comme une infrastructure de production : responsabilité claire, peu d’éléments mobiles, comportement mesurable et modèle d’échec connu.

Cible : trois colonnes qui se comportent

Nous voulons une mise en page avec :

  • Barre latérale gauche (navigation du site), sticky sous un en-tête, défilable indépendamment si elle est longue.
  • Contenu principal avec une longueur de ligne confortable et des blocs de code résilients.
  • TOC droit (titres de la page), sticky, défilable, qui ne vole pas le focus et ne nécessite pas de framework.

Et nous voulons qu’elle se dégrade élégamment : sur des écrans plus petits, s’effondrer en une seule colonne sans se transformer en labyrinthe de barres de défilement imbriquées.

Position tranchée : évitez une « coquille d’application pleine hauteur » avec height: 100vh + scroll interne pour des pages de docs. Ça casse le sticky et rend les ancres bizarres.

CSS Grid : le gabarit unique qui fonctionne

Il existe de nombreuses façons de faire cela. La plupart sont fragiles. Le pattern fiable est : laisser le body (ou le document) gérer le scroll, utiliser Grid pour les colonnes, et utiliser position: sticky pour les deux panneaux latéraux. Ensuite, limiter les largeurs avec des colonnes fixes pour les sidebars et une colonne centrale flexible avec minmax(0, 1fr).

Ce minmax(0, 1fr) n’est pas décoratif. Sans le 0, la colonne centrale peut refuser de se réduire à cause du dimensionnement intrinsèque, et vous obtenez un débordement. C’est le problème du type « pourquoi ma grille ignore ma largeur ».

Forme de la grille : [sidebar] [content] [toc]

Gabarit : grid-template-columns: 270px minmax(0, 1fr) 250px;

Gardez les sidebars comme éléments normaux dans le flux de la grille ; ne les positionnez pas en absolu. L’absolu paraît malin jusqu’à ce que vous essayiez d’imprimer, de gérer la hauteur dynamique de l’en-tête ou de supporter les safe areas sur mobile.

Structure HTML de référence

Minimale, ennuyeuse, stable :

cr0x@server:~$ cat layout.html
<header>...sticky top bar...</header>
<div class="layout">
  <nav>...left nav...</nav>
  <main>...content...</main>
  <aside>...right toc...</aside>
</div>

Le « secret » est que rien ici ne crée un nouveau contexte de défilement. C’est ainsi que le sticky survit au contact de l’ennemi.

Règles du sticky : ce qui le fait tenir (et ce qui le tue)

position: sticky est simple jusqu’à ce que ça ne le soit plus. Sticky fonctionne par rapport à l’ancêtre de défilement le plus proche. Si un ancêtre a overflow défini sur une valeur qui crée un conteneur de scroll (auto, scroll, hidden en pratique), sticky devient relatif à ce conteneur au lieu du viewport. Parfois c’est ce que vous voulez. La plupart du temps pour des docs, non.

Voici la configuration stable :

  • Scroll du document (pas de « scroll interne de l’app »).
  • Sidebars sticky avec top: headerHeight + gap.
  • Les sidebars ont max-height et overflow: auto pour que seules elles défilent si elles sont trop longues.

Et voici les tueurs classiques du sticky :

  • Un ancêtre avec overflow défini : un div wrapper avec overflow: hidden pour clipper une ombre, un gestionnaire de modales ou « corriger » un débordement horizontal.
  • Des transforms sur un ancêtre : transform et certains patterns filter/will-change peuvent affecter les blocs englobants et le comportement de défilement de façon surprenante.
  • Utiliser height: 100vh avec scroll interne : sticky devient relatif au conteneur de scroll interne, et les liens d’ancre n’atterrissent pas là où vous l’attendez.
Sticky est comme un contrôleur de baie de stockage : ça marche jusqu’à ce que quelqu’un ajoute « encore une couche » d’abstraction et que personne ne se souvienne pourquoi la latence a doublé.

Offsets de l’en-tête : ne faites pas d’hypothèses

Si vous avez un en-tête sticky, les sauts d’ancre atterriront sous lui. Définissez scroll-margin-top sur les titres et considérez le problème réglé. Ne le bidouillez pas avec des divs vides d’offset.

Exemple :

  • h2, h3 { scroll-margin-top: calc(var(--header-h) + 12px); }

Conteneurs de scroll : l’assassin silencieux du sticky

La plupart des bugs sticky ne sont pas des bugs sticky. Ce sont des bugs de conteneurs de scroll. Un conteneur de scroll est créé lorsqu’un élément clippe l’overflow et a une zone de débordement scrollable. Dans de vrais codebases, les conteneurs de scroll apparaissent parce que quelqu’un voulait :

  • empêcher le défilement horizontal depuis les blocs de code (overflow-x: hidden sur un wrapper)
  • appliquer un flou ou une ombre et le clipper
  • une « coquille pleine hauteur » pour que le footer ne bouge pas
  • un wrapper de virtualisation pour des résultats de recherche ou un widget de feedback

Votre travail est d’identifier l’ancêtre de défilement le plus proche de l’élément sticky. Dans Chrome DevTools vous pouvez le repérer à l’œil, mais en production vous voulez des vérifications reproductibles. Vous pouvez même ajouter un mode CSS de debug qui encadre les éléments avec des propriétés overflow. Faites-en un toggle au moment de la build.

Défilement imbriqué : choisissez exactement un gagnant

Les pages de docs devraient avoir exactement un défilement primaire : le document. Les sidebars peuvent être des régions de scroll secondaires si nécessaire, mais la page ne doit pas dépendre d’une région de scroll interne pour la navigation de base.

Le défilement imbriqué casse :

  • la restauration du scroll au retour/arrière du navigateur
  • les liens d’ancre
  • le comportement clavier PageDown
  • certaines attentes des lecteurs d’écran

Petite blague #1 : Les conteneurs de scroll imbriqués sont l’équivalent UI du RAID 0 : impressionnants pour transformer de petites erreurs en grosses conséquences.

Largeur de contenu lisible et contraintes typographiques

La colonne centrale est là où vivent vos lecteurs. Un grand écran ne signifie pas que vous devez l’exploiter entièrement. Limitez la largeur du contenu en caractères, pas en pixels. C’est un site de docs, pas un panneau publicitaire.

Règles qui fonctionnent :

  • Définissez max-width sur le flux de contenu, par ex. 74ch à 80ch.
  • Permettez aux blocs de code de défiler horizontalement à l’intérieur d’eux-mêmes. Ne « réparez » pas l’overflow en le masquant sur des wrappers externes.
  • Utilisez min-width: 0 (ou le minmax(0, 1fr) de Grid) pour que la colonne centrale puisse rétrécir.

De plus : les blocs de code sont la charge de travail de stockage des docs. Ils sont en pics, imprévisibles et pleins de cas pathologiques comme des chaînes non cassées. Traitez-les comme un test de performance : contraignez-les, isolez-les et mesurez-les.

TOC droit : balisage, offsets et états actifs

Un TOC à droite est utile lorsqu’il est discret. Il ne doit pas devenir un second système de navigation qui se bat avec votre nav principale. Gardez-le minimal :

  • Inclure seulement H2/H3. Sautez le H4 sauf si vous rédigez des documents de standardisation.
  • Tronquez visuellement les titres longs mais gardez le texte complet pour les lecteurs d’écran via aria-label si nécessaire.
  • Ne développez pas automatiquement un arbre en scrollant sauf si vous pouvez le faire sans thrash de layout.

Targets d’ancre : IDs stables, titres stables

Ne générez pas d’IDs à l’exécution côté client si vous pouvez l’éviter. Ça casse les liens entrants et rend les diffs bruyants. Générez les IDs au build time (static site generator, processeur markdown ou même un petit script dans votre pipeline). Si vous n’avez vraiment pas d’étape de build, vous pouvez toujours utiliser des règles déterministes, mais alors vos titres doivent être stables.

Mise en évidence de la section active : IntersectionObserver, pas du scroll math

Les scripts TOC old-school utilisaient des écouteurs de scroll et des vérifications manuelles de bounding rectangle. Ça marche jusqu’à ce que ça ne marche plus. Le primitif correct est IntersectionObserver, conçu pour répondre à la question « ce titre est-il visible ? ».

Cela dit : vous pouvez tout à fait éviter la mise en évidence active. Beaucoup d’équipes la publient puis passent des mois à l’ajuster. Si vous avez peu de temps, investissez-le dans le sticky et la typographie. Les lecteurs pardonnent un TOC passif. Ils ne pardonnent pas un TOC qui provoque du jank de scroll.

Comportement responsive sans framework

Sur des viewports étroits, une mise en page trois colonnes devient une parodie d’elle-même. Passez à une colonne. Vous pouvez soit :

  • empiler nav, contenu, toc (dans cet ordre), ou
  • cacher le TOC droit et fournir un mini-TOC dans le contenu près du haut

La seconde option est souvent meilleure : moins de régions de scroll, moins de bruit, moins de taps ratés.

Utilisez un breakpoint où la colonne de contenu devient étroite — typiquement autour de 1024px selon les largeurs des sidebars. Au-delà, traitez la vue comme « mobile » et simplifiez. N’optez pas pour « deux colonnes avec un TOC flottant » sauf si vous aimez déboguer des cas limites avec iOS Safari.

Accessibilité : skip links, focus, réduction des animations

Les mises en page de docs sont très axées navigation. Si vous construisez une coque sticky sophistiquée et oubliez les utilisateurs clavier, vous avez livré un labyrinthe dans le noir.

Skip links et rôles landmark

Fournissez un lien « Aller au contenu » comme premier élément focusable. Utilisez des éléments sémantiques (<nav>, <main>, <aside>) et étiquetez-les avec aria-label quand c’est utile.

Comportement du focus et scroll

Quand un utilisateur parcourt une sidebar sticky au clavier, les contours de focus doivent rester visibles et ne pas être rognés par l’overflow. Si votre nav est scrollable (overflow: auto), assurez-vous que les éléments focusés sont scrollés en vue. Les navigateurs gèrent généralement cela, mais cela peut échouer avec une gestion personnalisée du focus ou si vous appliquez des transforms étranges.

Réduction des animations

Si vous ajoutez un scroll smooth, respectez prefers-reduced-motion. Pensez aussi que le scrolling fluide peut sembler « lent » sur des appareils bas de gamme. La fiabilité vaut mieux que l’effet.

Performance et stabilité : le thrash de layout est aussi une panne

Les pages de docs ne sont pas censées faire fondre des laptops. Pourtant je les ai vues le faire, surtout à cause de :

  • des handlers de scroll qui font un travail lourd
  • des scripts TOC qui mesurent le layout à répétition (getBoundingClientRect) par frame
  • un re-run du highlight syntaxique lors de changements de route dans les SPA
  • des polices web provoquant un reflow tardif qui déplace les ancres

Deux règles difficiles :

  • N’exécutez pas des lectures et écritures de layout dans la même frame en réaction au scroll. Si vous devez, regroupez-les.
  • Privilégiez les primitives du navigateur (CSS sticky, IntersectionObserver) plutôt que le polling personnalisé.

« L’espoir n’est pas une stratégie. » — Gene Kranz

En termes ops : considérez la performance au scroll comme un budget. Si votre mise en page de docs provoque des tâches longues ou des recalculs de layout répétés, cela apparaîtra dans le monitoring réel comme une « lentitude mystérieuse », ce qui est le pire car difficile à reproduire.

Tâches pratiques avec commandes (et comment décider)

Ces tâches supposent que vous avez un environnement local, un artefact de build (même si c’est du HTML écrit à la main), et au moins un serveur statique. Les commandes sont celles que vous exécutez réellement pour déboguer le comportement de layout entre environnements. Chaque tâche inclut : la commande, ce que signifie la sortie, et la décision à prendre.

Task 1: Servir le site localement sans cache

cr0x@server:~$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

Ce que cela signifie : Vous servez des fichiers statiques sans astuces de service worker. Bon point de départ.

Décision : Reproduisez le bug sticky ici d’abord. S’il n’apparaît que derrière votre CDN/proxy réel, vous cherchez des balises/scripts injectés ou des headers/CSP différents.

Task 2: Vérifier la compression et le type de contenu (les scripts TOC cassent si le MIME est faux)

cr0x@server:~$ curl -I http://127.0.0.1:8080/index.html
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.2
Content-type: text/html
Content-Length: 24891
Last-Modified: Sat, 28 Dec 2025 10:11:41 GMT

Ce que cela signifie : MIME correct et Last-Modified stable. Certains navigateurs se comportent étrangement si le CSS ou JS est servi en text/plain.

Décision : Si le MIME est incorrect en production, corrigez la config du serveur avant de déboguer le CSS. Sinon vous déboguez des fantômes.

Task 3: Confirmer que les colonnes de la grille sont calculées comme prévu

cr0x@server:~$ node -e "console.log('Use DevTools: Elements → Computed → grid-template-columns')"
Use DevTools: Elements → Computed → grid-template-columns

Ce que cela signifie : Aucun CLI ne bat DevTools pour les pistes calculées de la grille. Vous vérifiez des overrides inattendus.

Décision : Si vous voyez quelque chose comme 270px 1000px 250px alors que vous attendiez que le milieu soit flexible, trouvez la règle qui a retiré minmax(0, 1fr) ou introduit un comportement min-content.

Task 4: Rechercher les règles overflow qui créent des conteneurs de scroll

cr0x@server:~$ rg -n "overflow\s*:\s*(auto|scroll|hidden)" ./styles
styles/app.css:41:  overflow: hidden;
styles/layout.css:112: overflow: auto;

Ce que cela signifie : Vous avez trouvé des suspects potentiels du sticky. overflow: hidden sur un ancêtre est un suspect majeur.

Décision : Pour chaque occurrence, vérifiez si elle enveloppe vos éléments sticky. Si oui, retirez-la ou restreignez son périmètre (par ex. seulement le bloc de code, pas le wrapper de layout entier).

Task 5: Identifier des wrappers inattendus générés par l’outil de build

cr0x@server:~$ rg -n "layout|wrapper|container|shell" ./dist/index.html
52:<div class="app-shell">
53:  <div class="page-wrapper">

Ce que cela signifie : Votre « page simple » est à l’intérieur d’une app shell. Cette shell possède souvent le scroll.

Décision : Si .app-shell utilise height: 100vh + overflow: auto, vous devez soit refactorer la shell, soit accepter que le sticky collera à l’intérieur de ce conteneur et ajuster les attentes (ancres, restauration du scroll, etc.). Je recommande de refactorer pour la doc.

Task 6: Vérifier si un parent utilise des transforms (peut casser fixed/sticky)

cr0x@server:~$ rg -n "transform\s*:" ./styles
styles/marketing.css:88: transform: translateZ(0);
styles/marketing.css:212: transform: scale(1.02);

Ce que cela signifie : Quelqu’un a appliqué des hacks de transform pour la « fluidité ». Souvent du cargo cult.

Décision : Si ces éléments transformés enveloppent le layout, retirez le transform ou déplacez-le dans un enfant. Retestez ensuite le sticky. Ne conservez pas les hacks GPU sans justification mesurée.

Task 7: Confirmer que la hauteur de l’en-tête correspond à l’offset attendu

cr0x@server:~$ rg -n "--header-h" ./styles
styles/app.css:17: --header-h: 56px;

Ce que cela signifie : Vous utilisez une variable CSS pour la hauteur de l’en-tête. Bien. Vérifiez maintenant que l’en-tête fait bien 56px sur tous les breakpoints.

Décision : Si l’en-tête se wrappe sur des écrans plus petits et devient plus grand, vous devez mettre à jour --header-h de façon responsive ou éviter de dépendre d’une valeur fixe en pixels (par ex. calculer via le layout, ou utiliser top avec une valeur plus petite et donner aux titres un scroll-margin-top qui compense).

Task 8: Repérer l’overflow horizontal causé par les blocs de code

cr0x@server:~$ rg -n "pre\s*\{|code\s*\{|white-space|word-break|overflow-x" ./styles
styles/code.css:9: pre { overflow-x: auto; }
styles/code.css:10: pre { white-space: pre; }

Ce que cela signifie : Vos blocs de code défileront en interne, ce qui est correct. Si vous voyez overflow-x: hidden sur des wrappers externes, c’est un mauvais signe.

Décision : Gardez le contrôle de l’overflow au niveau du bloc de code. Si le wrapper de layout cache l’overflow, le sticky peut se casser et les contours de focus être rognés.

Task 9: Valider que les ancres existent et sont uniques

cr0x@server:~$ rg -n "id=\"" ./dist/index.html | head
118:<h2 id="grid-core">CSS Grid core: the one template that works</h2>
156:<h2 id="sticky-rules">Sticky rules: what makes it stick (and what kills it)</h2>

Ce que cela signifie : Les IDs existent. Confirmez maintenant leur unicité.

Décision : Si les IDs se répètent, les navigateurs sauteront vers la première occurrence, votre TOC paraîtra « faux » et le débogage sera pénible. Corrigez cela au moment de la génération.

Task 10: S’assurer que la CSP ne bloque pas votre script TOC (commun en entreprise)

cr0x@server:~$ curl -I https://docs.example.internal/ | rg -i "content-security-policy"
Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'self'

Ce que cela signifie : Les scripts inline peuvent être bloqués. Si votre TOC dépend de JS inline, il ne s’exécutera pas.

Décision : Déplacez les scripts vers des fichiers statiques servis depuis 'self', ou ajoutez des nonces/hashes. Pour la doc, préférez un TOC sans JS et une amélioration progressive.

Task 11: Détecter les longues tâches côté client corrélées au scroll

cr0x@server:~$ google-chrome --enable-logging=stderr --v=1 2>&1 | head
[1228/101521.402:VERBOSE1:chrome_main_delegate.cc(764)] basic startup complete
[1228/101521.409:VERBOSE1:startup_browser_creator.cc(157)] Launched chrome

Ce que cela signifie : Ce n’est que le logging de démarrage ; le vrai travail est dans les enregistrements Performance de DevTools. Toutefois, lancer avec logs aide quand GPU/compositor est suspecté.

Décision : Si les enregistrements montrent des recalculs de layout répétés pendant le scroll, auditez les scripts TOC et tout écouteur de scroll. Remplacez par IntersectionObserver ou throttlez à des intervalles raisonnables.

Task 12: Confirmer qu’aucun service worker ne met en cache du CSS/JS cassé

cr0x@server:~$ rg -n "serviceWorker|navigator\.serviceWorker" ./dist
dist/app.js:14:navigator.serviceWorker.register("/sw.js")

Ce que cela signifie : Un service worker existe. Il peut pinner un ancien CSS provoquant un comportement « corrigé mais pas corrigé ».

Décision : Pour la doc, envisagez d’éviter les service workers sauf si l’offline est nécessaire. Si vous le gardez, implémentez du cache busting et un flux de mise à jour clair.

Task 13: Vérifier les headers HTTP de cache pour le CSS (un CSS obsolète provoque des régressions fantômes)

cr0x@server:~$ curl -I https://docs.example.internal/assets/app.css | rg -i "cache-control|etag|last-modified"
Cache-Control: public, max-age=31536000, immutable
ETag: "a8b3c-5f1c2d9b"

Ce que cela signifie : Le cache immutable est acceptable seulement si les fichiers sont hashés par contenu.

Décision : Si le nom de fichier est statique (par ex. app.css), retirez le caching immutable ou ajoutez du hashing. Sinon les corrections de layout n’arriveront pas chez les utilisateurs.

Task 14: Valider que votre HTML a un seul landmark main

cr0x@server:~$ tidy -errors -q ./dist/index.html | head
line 1 column 1 - Warning: missing 
 declaration
line 54 column 5 - Warning: 
should not appear as a child of

Ce que cela signifie : Des issues de structure HTML peuvent embrouiller les technologies d’assistance et parfois vos sélecteurs CSS. (Aussi : ajoutez le doctype.)

Décision : Corrigez les warnings structurels qui affectent les landmarks et les titres. Ne chassez pas les warnings cosmétiques sauf s’ils causent de vrais problèmes.

Task 15: Auditer la hiérarchie des titres (la qualité du TOC en dépend)

cr0x@server:~$ rg -n "

Ce que cela signifie : Les titres existent et semblent ordonnés. Si vous voyez h4 avant un h2, votre générateur de TOC fera des choses étranges.

Décision : Faites respecter l’ordre des titres dans les guidelines d’authoring et les vérifications CI. La discipline du contenu paie.

Playbook de diagnostic rapide

Quand la mise en page est cassée et que quelqu’un demande « est-ce un bug CSS », vous n’avez pas le temps pour les spéculations. Vous avez besoin d’un petit playbook qui trouve le goulot rapidement.

Première étape : confirmer qui gère le scroll

  • Vérifier : est-ce que la page défile sur body/html, ou y a-t-il un conteneur interne avec sa propre scrollbar ?
  • Pourquoi : sticky se comporte par rapport au conteneur de scroll le plus proche.
  • Action : s’il y a un conteneur de scroll interne, décidez de le supprimer (préféré pour la doc) ou d’ajuster les offsets sticky et ancres en conséquence.

Deuxième étape : trouver l’ancêtre overflow/transform le plus proche

  • Vérifier : inspectez la sidebar et le TOC ; remontez le DOM en cherchant overflow, transform, filter, contain.
  • Pourquoi : n’importe lequel de ces éléments peut changer le contexte contenant du sticky ou le clipper.
  • Action : retirez-le ou restreignez son périmètre ; n’appliquez pas d’overflow fixes sur les wrappers de layout sans raison.

Troisième étape : vérifier la taille des pistes de la grille et le comportement min-width

  • Vérifier : assurez-vous que la colonne centrale est minmax(0, 1fr) et que le contenu ou ses enfants n’ont pas de min-width forçant l’overflow.
  • Pourquoi : le dimensionnement intrinsèque des blocs de code et des chaînes longues détruit votre layout.
  • Action : mettez min-width: 0 sur les enfants de la grille si nécessaire, et isolez l’overflow aux blocs de code.

Quatrième étape : mesurer le jank de scroll

  • Vérifier : enregistrez une trace Performance pendant le scroll.
  • Pourquoi : les scripts TOC sont des coupables fréquents.
  • Action : remplacez les handlers de scroll par IntersectionObserver, ou supprimez la mise en évidence active si vous ne pouvez pas la rendre peu coûteuse.

Erreurs courantes (symptôme → cause → correction)

La barre latérale sticky ne colle pas du tout

Symptôme : la sidebar défile normalement.

Cause racine : top manquant sur l’élément sticky, ou le sticky est appliqué au mauvais nœud (par ex. une liste interne, pas le panneau).

Correction : Appliquez position: sticky et top: ... au conteneur de la sidebar. Assurez-vous qu’il n’est pas à l’intérieur d’un ancêtre transformé.

Le sticky fonctionne jusqu’à un certain point, puis lâche

Symptôme : la sidebar colle au départ, puis « lâche prise ».

Cause racine : l’élément sticky est contraint par la hauteur de son bloc contenant (souvent parce que la ligne de la grille ou le wrapper se termine plus tôt que prévu).

Correction : Gardez l’élément sticky dans le même contexte de scroll que le document principal. Évitez les wrappers avec overflow qui clipent. Assurez-vous que le conteneur de la grille couvre toute la hauteur du contenu (cela arrive naturellement si vous n’imposez pas de hauteurs).

Le TOC droit chevauche le footer ou la fin de page

Symptôme : le panneau TOC se pose sur le contenu du footer.

Cause racine : utilisation de position: fixed au lieu de sticky, ou un wrapper de layout qui ne réserve pas d’espace pour le TOC.

Correction : Utilisez sticky dans la cellule de la grille pour qu’il se termine naturellement quand le contenu se termine. Évitez le fixed sauf si vous voulez vraiment un panneau flottant.

La colonne de contenu provoque un scroll horizontal sur toute la page

Symptôme : vous pouvez défiler latéralement. Tout le monde déteste ça.

Cause racine : longues lignes de code ou chaînes non cassées, plus une piste de grille qui ne veut pas se réduire parce que la colonne du milieu est 1fr sans minmax(0, ...).

Correction : Utilisez minmax(0, 1fr). Mettez overflow-x: auto sur pre. Ne mettez pas overflow-x: hidden sur des wrappers externes comme “solution”.

Les ancres sautent sous l’en-tête

Symptôme : cliquer dans le TOC vous positionne avec le titre caché sous l’en-tête sticky.

Cause racine : absence de scroll-margin-top sur les titres.

Correction : Ajoutez scroll-margin-top avec la hauteur de l’en-tête + gap. Gardez cela en CSS, pas en JS.

La mise en évidence active du TOC est en retard ou flicker

Symptôme : la surbrillance saute entre les titres ou se met à jour en retard.

Cause racine : un écouteur de scroll qui fait trop de travail, ou des seuils mal ajustés ; fréquent aussi avec des hauteurs d’en-tête variables.

Correction : Utilisez IntersectionObserver et choisissez un root margin sensé (par ex. offset haut correspondant à l’en-tête). Ou supprimez la mise en évidence active.

Le scroll de la sidebar capte les événements de la molette

Symptôme : l’utilisateur fait défiler sur la sidebar et se retrouve « coincé » à faire défiler la nav.

Cause racine : les sidebars sont scrollables et interceptent wheel/touch events.

Correction : Acceptez que ce soit un comportement normal, mais gardez les sidebars courtes si possible ; envisagez des sections repliables. Évitez les hacks agressifs de scroll chaining sauf si vous savez ce que vous faites.

La sortie d’impression est illisible ou manque du contenu

Symptôme : la page imprimée inclut du bruit nav/TOC ou coupe du contenu.

Cause racine : panneaux sticky et backgrounds mal traités pour l’impression.

Correction : Ajoutez une règle @media print pour masquer nav/aside et enlever les fonds lourds et ombres.

Checklists / plan étape par étape

Étape par étape : construire la mise en page (ordre sensé)

  1. Commencer par du HTML sémantique : header, nav, main, aside. Pas de wrappers inutiles.
  2. Faire la grille : colonnes gauche et droite fixes ; centre avec minmax(0, 1fr).
  3. Définir les contraintes de lisibilité : limiter la longueur de ligne avec max-width sur le flux de contenu ; faire défiler les blocs de code en interne.
  4. Ajouter le comportement sticky : panneaux gauche et droit sticky avec top basé sur la hauteur de l’en-tête ; utiliser max-height + overflow: auto pour les longues listes.
  5. Corriger les offsets d’ancre : scroll-margin-top sur les titres.
  6. Collapse responsive : une colonne sous un breakpoint ; envisager de cacher le TOC droit.
  7. Passage accessibilité : skip link, style focus, labels de landmarks, navigation clavier.
  8. Passage performance : éliminer les handlers de scroll ; si vous devez mettre en évidence les titres actifs, utilisez IntersectionObserver.
  9. Durcissement production : vérifier les wrappers/scripts injectés ; verrouiller overflow et transforms autour du layout.

Checklist opérationnelle : quoi revoir dans les PR

  • Pas de nouveau overflow: hidden sur des wrappers haut niveau sans justification et captures d’écran sur les breakpoints.
  • Pas de nouvelles coquilles height: 100vh pour les pages de docs (sauf si vous acceptez les conséquences et les documentez).
  • Le template de la grille conserve minmax(0, 1fr) (ou min-width: 0 équivalent sur le contenu).
  • Les titres ont des IDs stables ; pas d’IDs dupliqués.
  • Des styles d’impression existent et masquent le fouillis de navigation.
  • Tout JS pour le TOC est optionnel et ne casse pas sans JS.

Trois mini-récits d’entreprise depuis le terrain

Mini-récit 1 : un incident causé par une mauvaise hypothèse

Une équipe avec laquelle j’ai travaillé a déployé une refonte de docs dans une « coquille d’application unifiée ». La shell avait un header fixe, une sidebar gauche et une région de contenu interne avec height: 100vh et overflow: auto. L’hypothèse était simple : « c’est ce que font les apps modernes, et le sticky fonctionnera dedans. »

Ça a marché. Dans Chrome. Sur desktop. Dans le chemin heureux où la hauteur du header ne change jamais.

Puis une bannière corporate a été injectée en haut pour un avis de conformité. La hauteur de l’en-tête a augmenté. Les liens d’ancre ont commencé à atterrir sous l’en-tête. Le TOC droit (qui utilisait un écouteur de scroll) calculait de mauvais offsets parce qu’il supposait que la racine du scroll était la fenêtre, pas le conteneur interne. Les utilisateurs cliquaient sur des titres et pensaient que le contenu manquait. Les tickets de support ont commencé à arriver avec des captures d’écran de « sections blanches » qui étaient en réalité cachées au-dessus du pli.

L’incident n’était pas un crash. C’était pire : une perte progressive de confiance. Les ingénieurs ont cessé de se fier aux docs. Ils demandaient à des collègues à la place, ce qui est une façon coûteuse de récupérer de l’information.

La correction a été ennuyeuse : supprimer le conteneur de scroll interne, laisser le document défiler, définir scroll-margin-top, et intégrer la bannière dans le flux normal pour que le layout s’adapte naturellement. L’hypothèse que « les patterns d’app shell sont universels » était la cause racine. La doc est orientée contenu, pas chrome.

Mini-récit 2 : une optimisation qui a mal tourné

Une autre organisation voulait un « scroll ultra fluide » sur de longues pages de docs. Quelqu’un a introduit un pattern : appliquer transform: translateZ(0) au wrapper principal pour forcer l’accélération compositeur, plus will-change: transform sur quelques panneaux. Le changement venait avec une belle vidéo démo.

En production, le TOC droit a commencé à se comporter de manière incohérente. Sur certaines machines, le sticky cessait de fonctionner quand le wrapper devenait un bloc contenant transformé. Sur d’autres, ça marchait mais le rendu du texte changeait légèrement (différences subpixel) et les titres se déplaçaient juste assez pour que les seuils d’IntersectionObserver commencent à flicker. La mise en évidence active des titres dansait entre deux sections quand l’utilisateur défilait doucement. Rien ne dit « ingénierie de qualité » comme un widget de navigation en crise d’identité.

La perf s’est aussi dégradée sur les dispositifs bas de gamme. Le navigateur gardait des couches supplémentaires actives à cause de will-change, augmentant la pression mémoire et provoquant parfois des pauses GC. Les utilisateurs décrivaient la page comme « saccadée », ce qui veut dire que votre optimisation est le problème.

Le rollback a été immédiat. La correction long terme a été disciplinée : garder les transforms hors des wrappers de layout, mesurer la perf de scroll avec une trace, et n’appliquer will-change qu’aux éléments qui animent réellement. La plupart des pages de docs n’ont pas besoin d’animations ; elles ont besoin de prévisibilité.

Mini-récit 3 : une pratique ennuyeuse mais correcte qui a sauvé la situation

Une équipe avait l’habitude, ennuyeuse mais utile : chaque changement de layout venait avec une petite page de test « invariants de layout » dans le repo. Ce n’était pas sophistiqué. Juste un fichier HTML unique qui incluait du contenu pathologique : très longues lignes de code, titres profondément imbriqués, une nav surdimensionnée, une fausse bannière en haut et un footer.

Avant de livrer, ils ouvraient cette page dans quelques navigateurs, redimensionnaient le viewport et testaient les ancres. Dix minutes. Les gens râlaient, doucement, comme font les ingénieurs.

Puis une mise à jour d’un widget de feedback tiers est arrivée et a enveloppé la page entière dans un conteneur avec overflow: hidden pour gérer ses propres animations. Le sticky aurait cassé. Mais comme l’équipe avait la page d’invariants et la vérifiait en staging avec les intégrations de production, la régression a été détectée avant la release.

La correction n’a pas été héroïque : restreindre le conteneur du widget pour qu’il n’enveloppe pas le layout docs, et enlever la règle overflow du wrapper haut niveau. La pratique ennuyeuse — garder une page de test pathologique et l’utiliser — a sauvé des jours de churn support et évité l’envoi d’une navigation cassée à tous les ingénieurs de l’entreprise.

Petite blague #2 : La seule chose plus sticky que position: sticky est un bug report intitulé « navigation docs cassée » déposé cinq minutes avant un gel de release.

Faits et contexte historique

  • CSS Grid est devenu largement utilisable dans les navigateurs modernes autour de 2017, remplaçant une décennie de hacks float et de systèmes de colonnes fragiles.
  • Avant Grid, les mises en page trois colonnes « holy grail » étaient typiquement construites avec des floats ou des modes display en table, nécessitant souvent des astuces d’ordre source.
  • position: sticky est né d’une fonctionnalité longtemps demandée parce que le fixed est trop brutal pour des UI en flux comme les sidebars.
  • Le comportement sticky dépend des conteneurs de scroll, et ces conteneurs sont devenus plus fréquents avec les shells SPA et les bibliothèques qui par défaut gèrent le scroll interne.
  • Le pattern minmax(0, 1fr) existe à cause des règles de dimensionnement intrinsèque ; sans lui, le contenu comme de longues lignes de code peut forcer des pistes de grille plus larges que le viewport.
  • Les unités de caractère (ch) sont un outil typographique pratique pour les docs car elles s’adaptent à la police et représentent mieux la longueur de ligne que les pixels.
  • IntersectionObserver a été introduit pour réduire l’abus des handlers de scroll, offrant au navigateur un moyen plus efficace d’observer les changements de visibilité.
  • Les sites de docs s’appuyaient historiquement sur du HTML rendu côté serveur car le linking, l’impression et l’accessibilité sont non négociables ; le routage client lourd est arrivé plus tard et a souvent régressé ces bases.

FAQ

1) Dois-je utiliser CSS Grid ou Flexbox pour cette mise en page ?

Utilisez Grid. C’est littéralement pour ça que Grid existe : deux pistes fixes et une piste flexible avec des gaps propres. Flexbox peut le faire, mais il devient plus fragile quand vous ajoutez des panneaux à défilement indépendant et l’effondrement responsive.

2) Pourquoi insister sur minmax(0, 1fr) ?

Parce que la taille minimale par défaut des éléments de grille peut être leur largeur intrinsèque. De longues lignes de code forceront l’overflow. minmax(0, 1fr) permet explicitement à la piste de rétrécir.

3) Est-ce acceptable de faire toute la page en coquille hauteur fixe avec scroll interne ?

Vous pouvez, mais vous achetez des problèmes : liens d’ancre, restauration du scroll, offsets sticky et bizarreries sur mobile. Pour la doc, je recommande de laisser défiler le document sauf contrainte spécifique.

4) Mon élément sticky fonctionne jusqu’à ce que j’ajoute overflow: hidden quelque part. Pourquoi ?

Parce que vous avez créé (ou changé) l’élément qui définit le contexte contenant du sticky. Le sticky est relatif à l’ancêtre de scroll le plus proche. Les règles d’overflow créent souvent cet ancêtre ou modifient le comportement de clipping.

5) Le TOC droit doit-il être généré avec JavaScript ?

Si vous avez une étape de build, générez-le à la build. Si vous n’en avez pas, gardez un TOC statique simple ou utilisez une amélioration progressive. Ne rendez pas la navigation basique dépendante du JS sur un site de docs, sauf si vous aimez expliquer ce choix plus tard.

6) Comment empêcher que le TOC soit trop haut ?

Donnez-lui max-height: calc(100dvh - topOffset - bottomGap) et overflow: auto. Laissez-le défiler en interne. S’il reste massif, vous avez trop de titres ; c’est un problème de conception de contenu, pas de CSS.

7) Comment éviter que le TOC chevauche le contenu sur les petits écrans ?

Effondrez en une colonne à un breakpoint et cachez le TOC droit ou déplacez un mini-TOC dans le contenu en haut. Tenter de garder trois colonnes sur mobile provoque des scrolls horizontaux accidentels et des taps ratés.

8) position: sticky nuit-il aux performances ?

Le sticky en lui-même est généralement ok. Les tueurs de perf sont les handlers de scroll, le thrash de layout et les effets de peinture lourds (ombres, filtres) sur de grands panneaux sticky. Mesurez avec une trace ; ne devinez pas.

9) Quid des safe areas et encoches sur mobile ?

Si vous avez un header sticky, pensez à utiliser les variables d’environnement comme les safe-area insets dans vos paddings. Mais gardez simple : les pages de docs ne devraient pas avoir de chrome fixe complexe sur mobile.

10) Puis-je supporter proprement l’impression avec cette mise en page ?

Oui. Masquez nav et TOC dans @media print, enlevez les fonds et ombres, et laissez les liens soulignés. L’impression est encore utilisée pour des audits, des revues et des runbooks d’incident — étonnamment souvent.

Étapes suivantes qui aident vraiment

Si vous voulez que cette mise en page survive en production, faites le travail ennuyeux :

  • Verrouillez le modèle de scroll : scroll du document, pas une coquille interne.
  • Rendez Grid résilient : sidebars fixes, minmax(0, 1fr) au centre, et évitez les surprises de sizing intrinsèque.
  • Rendez le sticky prévisible : pas d’overflow/transform autour des éléments sticky.
  • Corrigez les ancres proprement : scroll-margin-top sur les titres, pas des divs d’offset bricolées.
  • Gardez l’amélioration du TOC optionnelle : si la mise en évidence active provoque du jank, supprimez-la. Personne n’a été appelé en urgence parce qu’un TOC n’était pas lumineux.

Puis exécutez les mêmes vérifications que pour un service : reproduisez en local, vérifiez dans un environnement proche de la production avec scripts injectés, et conservez une page de test pathologique. Les docs sont de l’infrastructure. Traitez-les comme telles.

← Précédent
Proxmox « Impossible d’activer le stockage » : diagnostiquer LVM, NFS et CIFS correctement
Suivant →
Environnements de démarrage ZFS : le filet de sécurité pour mises à jour que Linux ignore

Laisser un commentaire