CSS Anchor Positioning: Tooltip e Popover Senza JavaScript

CSS Anchor Positioning: Tooltip e Popover Senza JavaScript

Perché CSS Anchor Positioning nel 2026

Per anni posizionare un tooltip o un popover ha richiesto JavaScript. Dovevi calcolare getBoundingClientRect(), gestire il resize della finestra, stare attento agli overflow e, se eri sfortunato, dipendere da librerie come Popper.js o Floating UI che aggiungevano decine di KB al bundle. Tutto questo per far apparire un testo sopra un bottone.

Con CSS Anchor Positioning quella stagione è finita. La specifica — ora in stato Candidate Recommendation nel W3C — ti permette di dichiarare un elemento come “ancora” e di posizionare qualsiasi altro elemento relativo a quell’ancora usando pura sintassi CSS. Niente JavaScript, niente librerie esterne, niente re-render.

Il supporto browser nel 2026 è ormai solido: Chrome e Edge coprono oltre il 70% del traffico desktop e mobile globale, Firefox ha implementato l’API in versione stabile dalla 135, e Safari Technology Preview ha attivato il flag per default. Se lavori su progetti che non devono supportare browser molto datati — o se vuoi iniziare a usarla con un fallback progressivo — CSS Anchor Positioning è pronta per la produzione.

In questa guida vediamo tutto: la sintassi base, i casi d’uso più comuni (tooltip, popover, dropdown, badge dinamici), come gestire la collision avoidance nativa con position-try, e come combinare l’API con la HTML Popover API per un risultato completamente dichiarativo.

Come Funziona: anchor-name e position-anchor

Il meccanismo è basato su due proprietà CSS fondamentali: anchor-name e position-anchor. La prima dichiara che un elemento è un’ancora e le assegna un nome; la seconda dice a un elemento posizionato a quale ancora fa riferimento.

Una volta stabilita la relazione, puoi usare la funzione anchor() per calcolare posizioni relative ai lati dell’ancora: anchor(top), anchor(bottom), anchor(left), anchor(right), anchor(center). Queste funzioni restituiscono valori calcolati che il browser aggiorna automaticamente quando l’ancora si sposta — per qualsiasi ragione, inclusi scroll e resize.

/* Elemento ancora */
.trigger-btn {
  anchor-name: --my-tooltip;
}

/* Elemento posizionato rispetto all'ancora */
.tooltip {
  position: absolute;
  position-anchor: --my-tooltip;

  /* Posiziona il bordo superiore del tooltip
     al bordo inferiore dell'ancora, centrato */
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;

  /* Fallback se l'ancora non e' supportata */
  display: none;
}

/* Mostra il tooltip al hover */
.trigger-btn:hover + .tooltip,
.trigger-btn:focus + .tooltip {
  display: block;
}

Noti che il position: absolute rimane obbligatorio — Anchor Positioning non cambia il modello di posizionamento CSS, lo estende. L’elemento deve essere comunque rimosso dal flusso normale. La novità è che il contesto di posizionamento non è più il primo antenato con position: relative, ma l’ancora dichiarata esplicitamente.

Questo risolve uno dei problemi storici dei tooltip: l’overflow hidden. Se il tuo trigger è dentro un container con overflow: hidden, un tooltip posizionato in modo classico viene tagliato. Con Anchor Positioning il tooltip può uscire liberamente dal container perché si posiziona rispetto all’ancora, non rispetto al parent.

Tooltip Accessibile con Solo CSS

Costruiamo un tooltip completo, accessibile e funzionale usando solo CSS. Per l’accessibilità usiamo l’attributo aria-describedby così come suggerito nella guida ai tooltip accessibili già pubblicata. Il punto di forza è che non dobbiamo scrivere nemmeno una riga di JavaScript per mostrarlo o nasconderlo.

<!-- HTML -->
<button
  class="btn-with-tooltip"
  aria-describedby="tooltip-save"
>
  Salva bozza
</button>

<div
  id="tooltip-save"
  class="tooltip"
  role="tooltip"
>
  Salva come bozza senza pubblicare
</div>
/* ── Ancora ── */
.btn-with-tooltip {
  anchor-name: --btn-save;
}

/* ── Tooltip ── */
.tooltip {
  /* Modello di posizionamento */
  position: fixed;       /* fixed per ignorare overflow dei parent */
  position-anchor: --btn-save;

  /* Posizione: sopra il bottone, centrato */
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% -8px;

  /* Stile */
  background: #1e293b;
  color: #f1f5f9;
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 0.8rem;
  white-space: nowrap;
  pointer-events: none;

  /* Nascosto di default */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.15s ease, visibility 0.15s ease;
}

/* Freccia del tooltip */
.tooltip::after {
  content: '';
  position: absolute;
  bottom: -6px;
  left: 50%;
  translate: -50% 0;
  border: 6px solid transparent;
  border-top-color: #1e293b;
  border-bottom: 0;
}

/* Mostra al hover/focus del bottone */
.btn-with-tooltip:hover + .tooltip,
.btn-with-tooltip:focus-visible + .tooltip {
  opacity: 1;
  visibility: visible;
}

/* Rispetta prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  .tooltip {
    transition: none;
  }
}

Nota l’uso di position: fixed invece di position: absolute. Con fixed il tooltip esce da qualsiasi contesto di stacking e overflow, il che lo rende robusto in qualsiasi layout. La funzione anchor() funziona correttamente anche con fixed.

Popover con HTML Popover API + Anchor Positioning

La combinazione più potente è usare CSS Anchor Positioning insieme alla HTML Popover API. Quest’ultima gestisce automaticamente la visibilità (mostra/nasconde), il focus trap, la chiusura con Escape e l’accessibilità. Anchor Positioning si occupa del posizionamento. Insieme formano una soluzione completamente dichiarativa per dropdown, context menu e pannelli informativi.

<!-- Bottone trigger: popovertarget punta all'id del popover -->
<button
  class="popover-trigger"
  popovertarget="settings-panel"
>
  Impostazioni ⚙
</button>

<!-- Popover: l'attributo popover lo rende gestito dal browser -->
<div
  id="settings-panel"
  class="settings-popover"
  popover
>
  <ul>
    <li><a href="#">Profilo</a></li>
    <li><a href="#">Notifiche</a></li>
    <li><a href="#">Tema scuro</a></li>
    <li><button>Disconnetti</button></li>
  </ul>
</div>
/* Ancora sul trigger */
.popover-trigger {
  anchor-name: --settings-btn;
}

/* Posizionamento del popover */
.settings-popover {
  position: absolute;
  position-anchor: --settings-btn;

  /* Allinea il bordo superiore del popover
     al bordo inferiore del trigger */
  top: anchor(bottom);
  right: anchor(right);   /* allinea a destra */
  translate: 0 8px;

  /* Reset stili browser di [popover] */
  margin: 0;
  border: 1px solid #334155;
  border-radius: 10px;
  background: #0f172a;
  color: #e2e8f0;
  padding: 8px 0;
  min-width: 200px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.4);

  /* Animazione entrata */
  transition: opacity 0.2s ease, translate 0.2s ease;
}

/* Stato chiuso (prima di :popover-open) */
.settings-popover:not(:popover-open) {
  opacity: 0;
  translate: 0 -4px;
  pointer-events: none;
}

/* Stato aperto */
.settings-popover:popover-open {
  opacity: 1;
  translate: 0 8px;
}

/* Voci del menu */
.settings-popover a,
.settings-popover button {
  display: block;
  width: 100%;
  padding: 10px 16px;
  text-decoration: none;
  color: #cbd5e1;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font-size: 0.9rem;
}

.settings-popover a:hover,
.settings-popover button:hover {
  background: #1e293b;
  color: #f8fafc;
}

Il browser gestisce automaticamente la chiusura del popover quando l’utente clicca fuori o preme Escape. Non c’è bisogno di un event listener. La pseudo-classe :popover-open ti permette di animare l’entrata e l’uscita del pannello. Questo si integra perfettamente anche con le tecniche di CSS avanzato per layout e animazioni.

Collision Avoidance con position-try

Uno dei problemi classici dei tooltip è la collisione con i bordi del viewport: se il trigger è in alto nella pagina, il tooltip deve spostarsi sotto; se è a sinistra, deve spostarsi a destra. CSS Anchor Positioning risolve questo con la regola at-rule @position-try e la proprietà position-try-fallbacks.

Definisci più posizioni alternative e il browser sceglie automaticamente quella che non va fuori dal viewport. È la collision avoidance nativa, senza JavaScript.

/* Posizione preferita: sopra il trigger */
.smart-tooltip {
  position: fixed;
  position-anchor: --trigger;

  bottom: anchor(top);
  left: anchor(center);
  translate: -50% -8px;

  /* Lista di fallback in ordine di preferenza */
  position-try-fallbacks:
    --below,
    --left,
    --right;
}

/* Fallback 1: sotto il trigger */
@position-try --below {
  top: anchor(bottom);
  bottom: auto;
  translate: -50% 8px;
}

/* Fallback 2: a sinistra del trigger */
@position-try --left {
  right: anchor(left);
  left: auto;
  bottom: anchor(center);
  top: auto;
  translate: -8px -50%;
}

/* Fallback 3: a destra del trigger */
@position-try --right {
  left: anchor(right);
  right: auto;
  bottom: anchor(center);
  top: auto;
  translate: 8px -50%;
}

/* Keyword shorthand (alternativa ai fallback custom):
   il browser prova flip-block, flip-inline, flip-start */
.tooltip-auto {
  position-try-fallbacks: flip-block, flip-inline;
}

Le keyword predefinite come flip-block e flip-inline sono le più semplici da usare: invertono la posizione sull’asse corrispondente. Per casi più complessi (es. tooltip con freccia che deve ruotare) devi usare i @position-try custom come nell’esempio sopra. Questo tipo di soluzione si sposa bene anche con i pattern di stili isolati con CSS @scope, dove ogni componente gestisce il proprio anchor namespace.

Anchor Positioning e anchor-size()

Oltre a posizionare un elemento rispetto all’ancora, puoi usare la funzione anchor-size() per dimensionare l’elemento posizionato in base alle dimensioni dell’ancora. Questo è utile per dropdown che devono avere la stessa larghezza del campo di input che li apre — un pattern classico nei select custom e negli autocomplete.

/* Input di ricerca */
.search-input {
  anchor-name: --search-field;
  width: 100%;
}

/* Dropdown suggerimenti */
.search-dropdown {
  position: absolute;
  position-anchor: --search-field;

  /* Allineamento */
  top: anchor(bottom);
  left: anchor(left);
  translate: 0 4px;

  /* Stessa larghezza dell'input */
  width: anchor-size(width);

  /* Altezza massima con scroll */
  max-height: 320px;
  overflow-y: auto;

  background: white;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0,0,0,0.12);
}

/* Voce del dropdown */
.search-dropdown li {
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
}

.search-dropdown li:hover {
  background: #f8fafc;
}

La funzione anchor-size(width) legge la larghezza calcolata dell’ancora e la assegna come valore. Funziona anche con height, block, inline, self-block e self-inline. Questo elimina l’ultimo frammento di JavaScript che tipicamente serviva: quello che sincronizzava la larghezza del dropdown con quella dell’input dopo il resize.

Vale la pena ricordare che se stai costruendo interfacce complesse con molti layer, le tecniche descritte in questa guida si integrano naturalmente con i pattern di Cascade Layers CSS per mantenere l’ordine degli stili gestibile a lungo termine.

Browser Support e Strategia di Adozione

CSS Anchor Positioning è disponibile senza flag in Chrome 125+, Edge 125+ e Firefox 135+. Safari Technology Preview ha il supporto attivato; il rilascio stabile è atteso nella prossima major version. Complessivamente questo rappresenta circa il 75-80% del traffico browser globale nel 2026.

La strategia di adozione consigliata è il progressive enhancement: usa @supports (anchor-name: --x) per fornire un’esperienza base con JavaScript (o senza tooltip) ai browser non supportati, e un’esperienza arricchita a quelli supportati.

/* Fallback base: tooltip visibile solo come title attribute */
.tooltip-wrapper {
  position: relative;
}

/* Enhancement: Anchor Positioning disponibile */
@supports (anchor-name: --x) {
  .trigger {
    anchor-name: --tooltip-anchor;
  }

  .tooltip-content {
    position: fixed;
    position-anchor: --tooltip-anchor;
    bottom: anchor(top);
    left: anchor(center);
    translate: -50% -8px;

    position-try-fallbacks: flip-block, flip-inline;

    /* Mostra il tooltip */
    opacity: 1;
    visibility: visible;
    pointer-events: none;
  }

  /* Nasconde il vecchio tooltip JS */
  .tooltip-js-fallback {
    display: none;
  }
}

/* Nasconde il tooltip CSS nei browser senza supporto */
@supports not (anchor-name: --x) {
  .tooltip-content {
    display: none;
  }
}

Questo approccio garantisce che i browser più datati continuino a funzionare senza errori JavaScript, mentre i browser moderni ottengono l’esperienza ottimale senza overhead.

  • Chrome 125+ — supporto completo senza flag
  • Edge 125+ — supporto completo senza flag
  • Firefox 135+ — supporto completo senza flag
  • Safari 19+ — supporto stabile atteso (Technology Preview attivo)
  • Safari < 19 — usa @supports not per fallback

FAQ e Domande Frequenti

CSS Anchor Positioning richiede JavaScript per funzionare?

No. CSS Anchor Positioning è una specifica puramente CSS. Il posizionamento e il tracking dell’ancora avvengono nel motore di rendering del browser senza necessità di JavaScript. Per mostrare o nascondere tooltip puoi usare combinazioni di :hover, :focus-visible e, per i popover, l’attributo HTML popover con popovertarget. L’unico caso in cui potresti aver bisogno di JavaScript è per logiche di business personalizzate, ad esempio aprire un popover in risposta a un evento asincrono.

Posso avere più tooltip che puntano alla stessa ancora?

Sì. Più elementi possono dichiarare lo stesso position-anchor e posizionarsi rispetto alla stessa ancora. Ogni elemento calcolerà la propria posizione indipendentemente. Però un singolo elemento può fare riferimento a una sola ancora alla volta: non è possibile usare anchor() con due ancore diverse per le proprietà top e left dello stesso elemento. Per casi complessi di questo tipo rimane necessario un piccolo layer JavaScript.

anchor-name funziona tra Shadow DOM diversi?

Per default no: l’associazione tra anchor-name e position-anchor è limitata allo stesso tree di composizione DOM. Gli elementi all’interno di uno Shadow DOM non possono fare riferimento ad ancore definite nel documento principale. Esistono proposte in corso nel W3C per estendere l’API con una modalità di “escaped anchor” che supererebbe questo limite, ma al momento non sono implementate nei browser. Se lavori con Web Components tienilo a mente.

CSS Anchor Positioning può sostituire Popper.js o Floating UI?

Per la maggior parte dei casi d’uso: sì. Tooltip semplici, popover, dropdown e badge posizionati dinamicamente non richiedono più librerie esterne. I vantaggi sono evidenti: zero KB di JavaScript, nessun flash di contenuto non posizionato (FOCP), nessun listener su scroll e resize. Le librerie come Floating UI mantengono il loro valore per scenari avanzati: ancore multiple, posizionamento 3D, o browser support allargato che include Safari < 19 e Firefox < 135. Valuta caso per caso.

Conclusione

CSS Anchor Positioning risolve un problema che ha afflitto il frontend per due decenni: posizionare elementi UI in modo dinamico rispetto ad altri elementi senza JavaScript. La specifica è ben progettata, il supporto browser è già significativo e la collision avoidance nativa con @position-try elimina l’ultimo motivo per usare librerie esterne per la maggior parte dei progetti.

Il passo successivo è iniziare con i casi semplici — un tooltip su un bottone, un popover di menu — e sperimentare la combinazione con la HTML Popover API. Con @supports il rollout può essere graduale e sicuro. Nei prossimi mesi, man mano che Safari raggiunge la copertura completa, CSS Anchor Positioning diventerà il pattern default per questo tipo di componenti. Meglio conoscerla adesso.

Suggerimenti e Risorse

🔧 Strumento: Testa il supporto Anchor Positioning sul tuo browser con la demo ufficiale su anchor-tool.netlify.app — visualizza in tempo reale come si comportano i fallback @position-try al ridimensionamento della finestra.

💡 Pro tip: Usa position: fixed invece di position: absolute per i tooltip: uscirà da qualsiasi overflow: hidden e transform nel parent, rendendo il componente robusto in qualsiasi contesto di layout.

🎯 Strategia: Combina sempre Anchor Positioning con la HTML Popover API per i componenti interattivi: il browser gestisce apertura/chiusura, focus management e accessibilità keyboard in modo nativo, senza una riga di JavaScript.

Condividi

Articoli Recenti

Categorie popolari