22 luglio 2026
Animare l’entrata/uscita da display: none in CSS (finalmente)
Con transition-behavior: allow-discrete e @starting-style puoi fare fade e movimenti sia in apertura che in chiusura, senza hack e senza JS.
Per anni il limite era chiaro: display è una proprietà “discreta” e non si poteva transizionare. Oggi si può gestire in modo pulito l’apertura e la chiusura di elementi che passano da display: none a display: grid/block, ottenendo animazioni complete (anche diverse tra enter e exit) grazie a transition-behavior: allow-discrete e alla nuova at-rule @starting-style. Ecco come impostare modali, popover e pannelli con un CSS chiaro e prevedibile, includendo anche l’animazione del backdrop.
Per molto tempo, ogni volta che volevamo animare un elemento che passa da display: none a “visibile”, finivamo in una palude di workaround: opacity + visibility, max-height finto, animazioni su wrapper, classi intermedie, setTimeout e simili.
Il motivo era semplice: display è una proprietà a “salti” (discreta). Non ha valori intermedi, quindi una transizione tradizionale non può interpolarla.
Oggi però possiamo ottenere un risultato sorprendentemente pulito con due ingredienti:
transition-behavior: allow-discrete;@starting-style(una nuova at-rule)
Il risultato: l’elemento può animare sia in uscita che in entrata, pur passando da display: none.
1) Il problema: perché in chiusura “quasi” funziona
Quando chiudi un componente e lo riporti a display: none, l’animazione tipica che vorresti è: fade-out, slide-out, ecc.
Con transition-behavior: allow-discrete puoi dire al browser: “Accetta transizioni anche se ci sono proprietà discrete nel mezzo”. In pratica il browser ritarda l’applicazione del salto discreto (es. il passaggio a display: none) finché le altre transizioni (come opacity o translate) non hanno finito.
Ecco perché spesso vedi subito un miglioramento in chiusura.
.panel {
transition: opacity 300ms ease, translate 300ms ease;
transition-behavior: allow-discrete;
}
.panel[hidden] {
display: none;
opacity: 0;
translate: 0 12px;
}
.panel:not([hidden]) {
display: grid;
opacity: 1;
translate: 0 0;
}
Ma in apertura potresti notare un comportamento “istantaneo”: se l’elemento era display: none, quando appare è già nello stato finale e non “sa” da dove partire.
2) La chiave per l’entrata: @starting-style
@starting-style serve esattamente a questo: definire lo stile di partenza per l’animazione di entrata, anche se l’elemento prima non partecipava al layout perché era display: none.
La cosa importante è che @starting-style deve stare dopo le regole dello stato “aperto”, perché conta la cascata (l’ultima regola vince). Se lo metti prima, rischi di annullarlo senza accorgertene.
Esempio completo:
.modal {
transition: opacity 350ms ease, translate 350ms ease;
transition-behavior: allow-discrete;
}
/* stato chiuso */
.modal[hidden] {
display: none;
opacity: 0;
translate: 0 24px;
}
/* stato aperto */
.modal:not([hidden]) {
display: grid;
opacity: 1;
translate: 0 0;
}
/* stile di partenza SOLO per l'entrata */
@starting-style {
.modal:not([hidden]) {
opacity: 0;
translate: 0 -24px;
}
}
Perché è potente
Notare un dettaglio interessante: qui l’entrata e l’uscita possono essere diverse.
- In entrata parti da
translate: 0 -24px(arriva dall’alto) - In uscita vai verso
translate: 0 24px(esce verso il basso)
È un pattern molto difficile da ottenere “bene” quando sei costretto a usare solo classi e proprietà alternative a display.
3) Modali, top layer e overlay: cosa ha senso dichiarare
Se stai lavorando con modali (ad esempio dialog, popover, componenti in top layer), in alcuni casi può essere utile dichiarare anche proprietà legate al layer/overlay quando passi allo stato aperto, per evitare comportamenti incoerenti tra browser e garantire che la UI “si agganci” correttamente al contesto.
Il punto pratico resta: display + transizioni su proprietà continue (opacity/transform/translate/filter) ora possono convivere senza hack.
4) Animare il backdrop: il trucco più semplice
Se hai un backdrop (ad esempio quello del dialog), spesso non si anima “da solo” come ti aspetti. La soluzione più robusta è: anima l’opacity del backdrop.
Esempio:
dialog::backdrop {
opacity: 0;
transition: opacity 350ms ease;
}
dialog[open]::backdrop {
opacity: 1;
}
Se sul backdrop usi anche filter (blur, saturazione, ecc.), spesso l’effetto percepito migliora comunque grazie alla transizione dell’opacità: il filtro sembra “entrare” con più naturalezza.
Sintesi operativa
transition-behavior: allow-discretepermette al browser di gestire correttamente il passaggio a proprietà discrete (comedisplay) durante una transizione.@starting-styledefinisce lo stato iniziale dell’animazione di entrata, anche quando l’elemento prima eradisplay: none.- Con questo approccio puoi creare enter/exit diversi, cosa che migliora tantissimo la qualità percepita di modali, pannelli, menu e popover.
- Per il backdrop, anima l’opacità: è semplice e funziona bene.
La conseguenza pratica è enorme: molte UI che prima richiedevano struttura extra o JavaScript per “pilotare” le classi possono tornare a essere CSS-first, leggibili e mantenibili, senza rinunciare ad animazioni fluide e coerenti.