Aller au contenu principal
MEWA STUDIO

Anchor positioning : la fin des librairies JavaScript pour tooltips et menus

Publié le 7 août 2026|9 min de lecture
CSSdéveloppementperformance

93 millions de téléchargements par semaine pour calculer la position d'un tooltip. Depuis janvier 2026, anchor positioning est supporté par tous les moteurs et le navigateur sait le faire seul. Construction d'un tooltip puis d'un menu complet en CSS natif, replis compris, puis la stratégie pour supprimer la dépendance sans casser les anciens navigateurs.

Rectangles aux contours néon bleus superposés sur un fond sombre, évoquant des panneaux d'interface ancrés les uns aux autres

93 millions de téléchargements en une semaine. C'est le volume mesuré le 7 août 2026 pour Floating UI (opens in a new tab), la librairie JavaScript de référence pour positionner tooltips, menus déroulants et popovers. Son prédécesseur Popper (opens in a new tab), pourtant remplacé par Floating UI, ajoute encore 24 millions de téléchargements hebdomadaires. Tout ce volume pour répondre à une question en apparence triviale : où placer cet élément par rapport à celui-là.

Depuis janvier 2026, cette question a une réponse native. Avec Firefox 147 (opens in a new tab), dernier moteur à l'implémenter, CSS anchor positioning est passé Baseline (opens in a new tab), le statut attribué quand une fonctionnalité devient disponible dans tous les navigateurs majeurs. Trois propriétés CSS suffisent désormais pour attacher un tooltip à son bouton, le faire suivre au défilement et le retourner de l'autre côté quand la place manque. Sans JavaScript.

Le sujet mérite mieux qu'une démonstration de syntaxe. Cet article commence par ce que ces librairies calculent réellement. Ce calcul explique pourquoi tant de tooltips finissent coupés par un bord d'écran. Il construit ensuite un tooltip puis un menu complet en CSS natif, repli compris quand la place manque. Il se termine par la question qui compte en production. Qui peut supprimer la dépendance dès aujourd'hui et qui doit encore la garder ?

Ce que Floating UI calcule à votre place

Positionner un tooltip sous son bouton avec huit pixels d'écart semble simple. La difficulté n'a jamais été là. En CSS, un élément en position: absolute se place par rapport à son ancêtre positionné le plus proche, pas par rapport à un élément arbitraire de la page. Pour coller un tooltip à un bouton, il fallait choisir entre deux compromis. Imbriqué dans le bouton, il hérite des overflow de ses parents et finit rogné par le premier conteneur qui coupe son contenu. Sorti à la racine du document, il échappe au rognage mais plus rien ne le relie au bouton, il faut alors mesurer des coordonnées en JavaScript.

C'est ce second chemin que l'écosystème a industrialisé. Voilà à quoi ressemble le contrat :

javascript
import { computePosition, offset, flip, shift } from '@floating-ui/dom';

function position() {
  computePosition(button, tooltip, {
    placement: 'top',
    middleware: [offset(8), flip(), shift({ padding: 8 })],
  }).then(({ x, y }) => {
    Object.assign(tooltip.style, {
      left: `${x}px`,
      top: `${y}px`,
    });
  });
}

Positionner un tooltip avec Floating UI

La fonction calcule une position une fois. Or une position n'est jamais acquise : le visiteur défile, la fenêtre change de taille, un conteneur intermédiaire défile lui aussi, le contenu bouge. La librairie doit donc relancer ce calcul en continu, en écoutant le défilement et le redimensionnement de chaque ancêtre défilable. Chaque relance lit des dimensions avec getBoundingClientRect puis écrit des styles, le cycle exact de lecture-écriture qui alimente le layout thrashing (opens in a new tab) sur le thread principal.

Ce contrat coûte sur trois plans. Le module de base pèse 8,2 ko compressés (opens in a new tab), avant le moindre composant construit dessus. Ses écouteurs travaillent pendant le défilement, le pire moment pour occuper le navigateur. Et sa fiabilité dépend d'une liste de cas limites sans fin, un ancêtre en transform qui déplace le référentiel, un conteneur sticky, un zoom, une iframe. Chaque tooltip croisé un jour figé au mauvais endroit après un défilement est l'un de ces cas limites qui a échappé au calcul.

Trois propriétés qui remplacent le calcul

Anchor positioning inverse le modèle. Au lieu de mesurer des coordonnées, on déclare une relation : cet élément est ancré à celui-là. Le navigateur maintient la relation lui-même, à chaque frame, quel que soit le défilement. Le tooltip complet :

css
.button {
  anchor-name: --button;
}

.tooltip {
  position: fixed;
  position-anchor: --button;
  position-area: block-start;
  margin-block-end: 8px;
}

Un tooltip ancré en CSS natif

anchor-name déclare l'ancre, avec un nom en double tiret comme une variable CSS. position-anchor relie l'élément positionné à cette ancre. position-area le place sur une grille imaginaire de neuf cases centrée sur le bouton, trois lignes sur trois colonnes, la case du milieu étant le bouton lui-même. block-start désigne la ligne du haut, le tooltip s'affiche donc au-dessus, centré. Le margin-block-end recrée l'écart de huit pixels, l'équivalent du middleware offset(8).

Deux détails conditionnent le fonctionnement. L'élément positionné doit être en position: fixed ou absolute, sinon les propriétés d'ancrage sont ignorées. Et l'ancre doit être rendue avant l'élément positionné dans l'ordre du document, une contrainte rarement gênante pour un tooltip placé juste après son bouton dans le HTML.

IntentionValeur logiqueValeur physique
Au-dessus, centréblock-starttop
En dessous, centréblock-endbottom
À droite, centréinline-endright
En dessous, calé sur le bord gauche du boutonblock-end span-inline-endbottom span-right

Les quatre placements les plus courants d'un élément ancré.

Pour un contrôle au pixel plutôt qu'à la case, la fonction anchor() expose directement les bords de l'ancre dans les propriétés d'inset :

css
.tooltip {
  position: fixed;
  position-anchor: --button;
  bottom: anchor(top);
  left: anchor(left);
}

Contrôle fin avec la fonction anchor()

bottom: anchor(top) colle le bas du tooltip au haut du bouton, left: anchor(left) aligne leurs bords gauches. Les deux approches se combinent avec calc() pour les décalages fins. Quant au suivi au défilement, il ne demande rien puisque le navigateur repositionne l'élément ancré lui-même. Ce qui exigeait des écouteurs, des mesures et des écritures de style à chaque frame devient une garantie du moteur de rendu. Le guide MDN (opens in a new tab) détaille l'ensemble des valeurs disponibles.

Le menu complet : popover pour l'autre moitié du problème

Le positionnement n'était que la moitié du travail d'un menu déroulant. L'autre moitié consiste à passer au-dessus du reste de la page. Un menu imbriqué dans un header en position: sticky avec un z-index modeste finira derrière un élément à l'empilement plus agressif ou rogné par un overflow: hidden. L'attribut HTML popover règle cette moitié en projetant l'élément dans la top layer, une couche au-dessus de tout le document qu'aucun z-index et aucun overflow ne peuvent contrarier. Combiné à l'ancrage :

html
<button popovertarget="menu" class="trigger">
  Options
</button>

<nav id="menu" popover>
  <a href="/profil">Profil</a>
  <a href="/reglages">Réglages</a>
  <a href="/deconnexion">Déconnexion</a>
</nav>

Un menu déroulant sans JavaScript

css
.trigger {
  anchor-name: --menu;
}

#menu {
  position-anchor: --menu;
  position-area: block-end span-inline-end;
  width: anchor-size(width);
  margin: 8px 0 0;
}

Le CSS du menu ancré

Le navigateur fournit le comportement : clic sur le bouton pour ouvrir, clic ailleurs ou touche Échap pour fermer, gestion de l'empilement si plusieurs popovers s'ouvrent. anchor-size(width) aligne la largeur du menu sur celle du bouton, un classique des menus de sélection qui demandait encore une mesure JavaScript. Le margin: 8px 0 0 remet à zéro les marges par défaut du popover tout en conservant l'écart vertical.

Un point d'honnêteté s'impose. popover gère l'ouverture et la fermeture, pas la sémantique d'un menu au clavier. Un vrai menu d'application, avec navigation aux flèches et rôles ARIA, garde besoin de JavaScript pour son comportement. Ce qui disparaît, c'est le JavaScript de positionnement, la partie fragile et coûteuse.

Quand la place manque : le repli déclaratif

Reste le service le plus visible des librairies, retourner le tooltip quand il déborde de l'écran. Un tooltip configuré au-dessus d'un bouton situé en haut de page doit basculer en dessous. C'est le middleware flip() de Floating UI. La version déclarative :

css
.tooltip {
  position-area: block-start;
  position-try-fallbacks: flip-block, flip-inline;
}

Basculer quand l'espace manque

position-try-fallbacks liste des positions de secours que le navigateur essaie dans l'ordre dès que la position principale déborde de la zone visible. flip-block retourne le placement sur l'axe vertical, flip-inline sur l'axe horizontal. Pour un repli plus spécifique qu'un simple miroir, @position-try déclare une position alternative complète :

css
@position-try --to-the-right {
  position-area: inline-end;
  width: anchor-size(width);
}

#menu {
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block, --to-the-right;
}

Une position de repli sur mesure

Dernière pièce, position-visibility: anchors-visible masque l'élément positionné quand son ancre sort de l'écran, le comportement attendu d'un tooltip dont le bouton a défilé hors de vue. L'ensemble couvre l'essentiel de ce que les middlewares flip, shift et hide faisaient en JavaScript, dans une syntaxe que le navigateur optimise lui-même.

Ce que JavaScript fait encore mieux

L'honnêteté impose l'inventaire inverse. Trois cas gardent une longueur d'avance côté JavaScript.

  • Suivre le curseur ou un point arbitraire. Un menu contextuel qui s'ouvre à l'endroit du clic n'a pas d'élément ancre : Floating UI accepte des ancres virtuelles, de simples coordonnées. CSS exige un élément réel dans le document
  • Les collisions sur mesure. position-try-fallbacks choisit parmi des positions déclarées d'avance. Une logique fine, comme éviter une zone précise de l'interface ou glisser continûment le long d'un axe, reste le territoire des middlewares JavaScript
  • Les composants de librairies tierces. Bootstrap exige Popper (opens in a new tab) pour ses dropdowns et la plupart des kits de composants React embarquent Floating UI en dépendance transitive. Cette dépendance ne partira pas d'un refactoring local : elle partira quand ces projets migreront, mise à jour après mise à jour. C'est d'ailleurs ce que racontent les 93 millions de téléchargements : très peu de développeurs installent la librairie eux-mêmes, elle arrive dans les bagages d'un composant

L'adopter aujourd'hui, sans rien casser

Le support s'est construit en trois étapes, Chrome et Edge 125 (opens in a new tab) en mai 2024, Safari 26 (opens in a new tab) le 15 septembre 2025, puis Firefox 147 (opens in a new tab) en janvier 2026. Au 7 août 2026, Caniuse (opens in a new tab) mesure environ 82 % de couverture mondiale. Le statut exact reste "Newly available", à distinguer du label "Widely available" que Baseline n'accorde qu'après trente mois de support généralisé. Autrement dit, une part réelle du trafic navigue encore avec des versions antérieures. Trois stratégies couvrent cette réalité, de la plus légère à la plus prudente.

Le repli dégradé applique directement le progressive enhancement (opens in a new tab). Les navigateurs récents reçoivent le tooltip ancré, les autres une version simplifiée, positionnée dans le flux sous le déclencheur ou remplacée par l'attribut title. Le test tient en une requête de fonctionnalité :

css
@supports (anchor-name: --x) {
  .tooltip {
    position: fixed;
    position-anchor: --button;
    position-area: block-start;
    position-try-fallbacks: flip-block;
  }
}

Progressive enhancement avec @supports

Le polyfill quand l'expérience doit être identique partout. OddBird (opens in a new tab) maintient un polyfill qui lit les propriétés d'ancrage dans les feuilles de style et les émule en JavaScript sur les navigateurs qui les ignorent. Chargé conditionnellement, il ne pèse rien pour les navigateurs récents :

html
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>

Charger le polyfill uniquement si nécessaire

Le statu quo surveillé vaut pour les composants issus d'un design system tiers, dont la migration appartient au projet amont, pas au projet qui le consomme. Le geste utile consiste plutôt à ne plus ajouter la librairie pour ses propres besoins. Le prochain tooltip maison n'a pas besoin de huit kilo-octets de JavaScript.

La décision se résume ainsi :

  • Nouveau projet ou audience grand public à navigateurs récents : natif avec repli @supports, aucune dépendance
  • Parc de navigateurs vieillissant, intranet, secteur public : natif plus polyfill, l'expérience reste identique partout
  • Menu contextuel au curseur ou logique de collision sur mesure : Floating UI reste le bon outil
  • Composants issus d'un kit tiers : attendre ses mises à jour et ne rien installer de nouveau

Le navigateur finit toujours par rattraper

Anchor positioning suit une trajectoire déjà vue. jQuery a cédé ses sélecteurs à querySelector, les carrousels JavaScript ont cédé le défilement à scroll-snap et les animations au scroll se réécrivent en CSS natif (opens in a new tab). À chaque itération, une couche de JavaScript devenue de la plomberie retourne dans le navigateur, plus rapide et plus fiable que la meilleure implémentation en librairie, parce qu'elle s'exécute dans le moteur de rendu plutôt qu'au-dessus de lui. Les développeurs ne s'y trompent pas. Dans l'enquête State of CSS 2026 (opens in a new tab), anchor positioning affiche la plus forte progression d'usage d'une année sur l'autre de toutes les fonctionnalités suivies (+15 %).

Ce mouvement dessine une répartition saine des rôles. La plomberie, mesurer, suivre, retourner, revient au navigateur. Le travail de conception reste entier, décider ce qu'un menu révèle, quand un tooltip aide au lieu d'encombrer, comment une interface répond à la main qui la parcourt. Un site se distingue par ce second travail. Autant ne plus payer le premier.