Einblend-Animationen für neu eingefügte Elemente
Jahrelang brauchte man JavaScript, um ein Element beim Einblenden aus display:none zu animieren. CSS @starting-style löst dieses Problem nativ — und ermöglicht elegante Übergänge für Popover, Dialoge und dynamisch eingefügte Inhalte ohne eine Zeile JavaScript.
Inhaltsverzeichnis
- 1. Das Problem: Warum display:none keine Transition kennt
- 2. @starting-style: Syntax und Grundprinzip
- 3. display:none Übergang mit allow-discrete
- 4. Popover API und @starting-style
- 5. Dialog-Element animieren
- 6. Neu eingefügte DOM-Elemente animieren
- 7. Das ::backdrop und overlay-Layer
- 8. @starting-style vs. JavaScript-Ansätze im Vergleich
- 9. Browser-Support und Progressive Enhancement
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem: Warum display:none keine Transition kennt
CSS-Transitions funktionieren, indem der Browser zwei Zustände eines Elements kennt und zwischen ihnen interpoliert. Wechselt ein Element von opacity: 0 zu opacity: 1, berechnet der Browser Zwischenwerte und erzeugt eine flüssige Animation. Dieses Prinzip funktioniert bei allen kontinuierlichen Eigenschaften wie opacity, transform oder color reibungslos. Die Eigenschaft display hingegen ist diskret — sie kennt keine Zwischenwerte. Ein Element ist entweder sichtbar oder unsichtbar, ohne Übergang dazwischen.
Das klassische Workaround-Muster war jahrelang folgendes: Man setzt opacity: 0 und visibility: hidden, startet einen JavaScript-Timeout von einem Frame, und setzt dann die Klasse, die die Einblend-Animation auslöst. Oder man nutzt requestAnimationFrame zweimal hintereinander. Diese Lösungen sind fragil, benötigen JS-Code, der eigentlich reine Darstellungslogik enthält, und erzeugen häufig Layout-Flicker. Genau hier setzt CSS @starting-style an: Die At-Rule definiert Anfangszustände für Eigenschaften, bevor das erste Mal ein Style auf ein Element angewendet wird — und ermöglicht so echte Einblend-Animationen für Elemente, die bisher keine CSS-Lösung hatten.
2. @starting-style: Syntax und Grundprinzip
Die @starting-style At-Rule definiert Styles, die für genau ein einzelnes Rendering-Frame gelten: den allerersten, in dem ein Element gerendert wird. Der Browser vergleicht diesen Startzustand mit dem Zielzustand und kann eine Transition zwischen beiden berechnen. Das ist konzeptuell so, als würde man dem Browser mitteilen: "Wenn dieses Element zum allerersten Mal auftaucht, beginne hier." Die Syntax ist verschachtelt innerhalb der normalen CSS-Regel oder alternativ als eigenständige Block-At-Rule.
Das Grundprinzip: @starting-style gilt ausschließlich für den ersten Style-Berechnung eines Elements. Wenn das Element bereits im DOM ist und sich ändert, spielt @starting-style keine Rolle mehr. Es gibt exakt zwei Auslöser für @starting-style: Ein Element wird neu ins DOM eingefügt (z.B. via JavaScript oder beim ersten Rendern der Seite), oder ein Element wechselt von display: none zu einem sichtbaren display-Wert. In beiden Fällen behandelt der Browser die @starting-style-Deklarationen als den "vor"-Zustand der Transition.
/* Basic @starting-style syntax — two equivalent forms */
/* Form 1: Nested inside the target rule */
.toast {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease, transform 0.3s ease;
@starting-style {
/* Starting state: element starts invisible and shifted down */
opacity: 0;
transform: translateY(16px);
}
}
/* Form 2: Standalone block (useful for specificity control) */
@starting-style {
.toast {
opacity: 0;
transform: translateY(16px);
}
}
/* Works when element is inserted into DOM or transitions from display:none */
.notification-panel {
display: block;
opacity: 1;
scale: 1;
transition:
opacity 0.25s ease-out,
scale 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
@starting-style {
opacity: 0;
scale: 0.9;
}
}
Ein wichtiger Unterschied zu Keyframe-Animationen: @starting-style arbeitet mit dem Transition-System, nicht mit dem Animation-System. Das bedeutet, man hat die volle Kontrolle über Easing, Duration und Delay via transition-Property — und das ohne eine separate @keyframes-Definition. Die Ausblend-Animation (vom sichtbaren Zustand zu display: none) muss hingegen anders gelöst werden, dazu mehr in Abschnitt 3.
3. display:none Übergang mit allow-discrete
Damit ein Übergang zu oder von display: none überhaupt stattfinden kann, muss die transition-Deklaration das Schlüsselwort allow-discrete enthalten. Ohne dieses Schlüsselwort ignoriert der Browser diskrete Eigenschaften wie display und visibility in Transitions komplett. Mit allow-discrete signalisiert man dem Browser: "Diese Eigenschaft soll zwar animiert werden, aber sie ist diskret — behandle sie entsprechend." Für diskrete Eigenschaften bedeutet das: Beim Einblenden springt display sofort zu Beginn der Transition auf den Zielwert, damit das Element sichtbar wird und die eigentliche Animation (opacity, transform) stattfinden kann. Beim Ausblenden bleibt display bis zum Ende der Transition auf dem sichtbaren Wert, damit die Ausblend-Animation vollständig abgespielt werden kann.
Diese Reihenfolge-Semantik ist entscheidend: Beim Einblenden (none → block) springt display sofort, @starting-style liefert den Startzustand für opacity etc. Beim Ausblenden (block → none) läuft die opacity-Transition zuerst durch, dann springt display. Das Zusammenspiel von @starting-style, allow-discrete und der Transition-Deklaration ergibt ein vollständiges System für bidirektionale Einblend- und Ausblend-Animationen — rein in CSS.
/* Bidirectional show/hide animation with allow-discrete */
.dropdown-menu {
display: none;
opacity: 0;
transform: translateY(-8px) scale(0.97);
}
.dropdown-menu.is-open {
display: block;
opacity: 1;
transform: translateY(0) scale(1);
/* allow-discrete enables discrete property (display) transitions */
transition:
opacity 0.2s ease-out,
transform 0.2s ease-out,
display 0.2s allow-discrete;
}
/* @starting-style defines entry state when display flips from none → block */
@starting-style {
.dropdown-menu.is-open {
opacity: 0;
transform: translateY(-8px) scale(0.97);
}
}
/* Exit animation runs automatically when .is-open is removed:
opacity and transform animate back, display stays block until done,
then jumps to none — no JavaScript timer needed */
4. Popover API und @starting-style
Die Popover API bringt das HTML-Attribut popover in Browser, mit dem Elemente deklarativ als Popover gekennzeichnet werden. Ein Popover wechselt zwischen zwei Zuständen: display: none (geschlossen) und einem sichtbaren display-Wert (offen). Genau dieser Übergang ist der ideale Anwendungsfall für @starting-style. Das Popover wird über das Pseudoklassen-Selector :popover-open im offenen Zustand gestylt.
Die @starting-style-Einblend-Animation für Popover funktioniert identisch zum allgemeinen Muster: Man definiert den Zielzustand in :popover-open und den Startzustand in @starting-style { :popover-open }. Wichtig: Der Browser verwaltet das Anzeigen und Verbergen des Popovers intern — man kann dem Popover-Element keinen eigenen Transition-State hinzufügen, ohne das :popover-open-Pseudoklassen-System zu nutzen. Für die Ausblend-Animation mit Popover gilt zusätzlich: Das Element verlässt den :popover-open-Zustand, und die Transition von diesem Zustand weg muss im :popover-open-Block selbst definiert sein, damit der Browser sie kennt.
5. Dialog-Element animieren
Das native HTML <dialog>-Element verhält sich ähnlich wie ein Popover: Es wechselt zwischen display: none und einem sichtbaren Zustand, wenn showModal() bzw. close() aufgerufen wird. Die Pseudoklasse :modal greift auf den offenen Zustand zu, @starting-style definiert den Einblende-Ausgangspunkt. Ein häufiger Fehler: Das Dialog-Element hat im Browser oft voreingestellte Styles, die man explizit überschreiben muss. Außerdem muss das ::backdrop-Pseudoelement separat animiert werden.
Besonders elegant ist die Kombination aus Dialog-Animation und Backdrop-Animation mit @starting-style: Beide Elemente können unabhängig voneinander Einblend-Animationen erhalten, die perfekt synchronisiert laufen, weil sie alle zum selben Zeitpunkt — dem Öffnen des Dialogs — ausgelöst werden. Kein JavaScript-Timing, kein requestAnimationFrame, keine Race Conditions zwischen Backdrop und Dialog-Inhalt.
/* Animated dialog with backdrop using @starting-style */
dialog {
border: none;
border-radius: 16px;
padding: 2rem;
max-width: min(90vw, 480px);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
/* Exit animation: dialog transitions out when :modal is removed */
transition:
opacity 0.25s ease,
transform 0.25s ease,
overlay 0.25s allow-discrete,
display 0.25s allow-discrete;
/* Default (closed) state — also serves as exit target */
opacity: 0;
transform: scale(0.95) translateY(16px);
}
dialog:modal {
/* Open state: fully visible and in position */
opacity: 1;
transform: scale(1) translateY(0);
}
@starting-style {
dialog:modal {
/* Entry animation starts from here */
opacity: 0;
transform: scale(0.95) translateY(16px);
}
}
/* Animate the backdrop separately */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0);
transition: background-color 0.25s ease, overlay 0.25s allow-discrete, display 0.25s allow-discrete;
}
dialog:modal::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
@starting-style {
dialog:modal::backdrop {
background-color: rgba(0, 0, 0, 0);
}
}
6. Neu eingefügte DOM-Elemente animieren
Der zweite Auslöser für @starting-style ist das erstmalige Einfügen eines Elements ins DOM. Wenn JavaScript ein Element erstellt und es an den DOM anhängt, gilt das @starting-style-Regelwerk für genau den ersten Render-Frame dieses Elements. Das macht @starting-style zur idealen Lösung für Listen-Einblende-Animationen, Toast-Benachrichtigungen, dynamisch nachgeladene Inhalte oder Skeleton-to-Content-Übergänge.
Ein praktischer Anwendungsfall: Eine Liste von Karten, die per API nachgeladen werden. Jede Karte soll beim Einfügen ins DOM eingeblendet werden. Mit @starting-style genügt eine reine CSS-Regel — kein JavaScript muss eine Klasse nach einem Timeout hinzufügen. Der Browser erkennt das Einfügen ins DOM, wendet @starting-style an und startet die Transition automatisch. Für gestaffelte Einblend-Effekte (Staggering) kombiniert man @starting-style mit animation-delay und dem :nth-child-Selektor oder setzt die --delay Custom Property via JavaScript einmalig beim Einfügen.
7. Das ::backdrop und overlay-Layer
Mit der Einführung der Popover API und dem Ausbau des <dialog>-Elements wurde auch ein neues CSS-Konzept eingeführt: der overlay-Layer. Elemente im Overlay-Layer (Popovers, modale Dialoge) werden stets über dem regulären Stack-Context gerendert, unabhängig von z-index-Werten im normalen DOM. Die Eigenschaft overlay kann in transition mit allow-discrete verwendet werden, um zu steuern, wann ein Element den Overlay-Layer verlässt.
Praktisch bedeutet das: Wenn ein Dialog schließt, bleibt er im Overlay-Layer, bis die Ausblend-Transition vollständig abgespielt ist — andernfalls würde er hinter anderen Elementen verschwinden, bevor die Animation endet. Die Kombination overlay 0.25s allow-discrete in der Transition-Deklaration sorgt dafür, dass der Browser das Element erst nach Ablauf der Transition aus dem Overlay-Layer entfernt. Dieses Detail ist oft der Grund, warum @starting-style-Animationen an Dialogen und Popovers ohne overlay allow-discrete nicht wie erwartet funktionieren.
8. @starting-style vs. JavaScript-Ansätze im Vergleich
Bevor @starting-style verfügbar war, gab es verschiedene JavaScript-basierte Ansätze, um Einblend-Animationen für Elemente aus display: none zu realisieren. Die Wahl des richtigen Ansatzes ist heute eine Abwägung zwischen Browser-Support-Anforderungen und Code-Komplexität.
| Ansatz | JavaScript nötig | Ausblend-Animation | Browser-Support 2026 |
|---|---|---|---|
| @starting-style | Nein | Ja (allow-discrete) | Chrome 117+, FF 129+, Safari 17.4+ |
| requestAnimationFrame (doppelt) | Ja | Manuell per JS | Alle Browser |
| CSS Keyframes + JS-Klasse | Ja (Klasse setzen) | Separate Keyframes | Alle Browser |
| visibility + opacity | Nein | Ja | Alle Browser (kein echter display:none) |
| Web Animations API | Ja (imperativ) | Ja | Alle modernen Browser |
Der entscheidende Vorteil von @starting-style gegenüber allen JavaScript-Ansätzen ist nicht nur die geringere Code-Menge, sondern die enge Kopplung an das CSS-Transition-System. Performance-Optimierungen wie will-change: opacity, transform werden automatisch korrekt auf die animierten Eigenschaften angewendet. Die Animation läuft auf dem Compositor-Thread, wenn möglich — ohne dass JavaScript beteiligt ist, das den Main Thread blockieren könnte.
9. Browser-Support und Progressive Enhancement
@starting-style ist seit Chrome 117 (September 2023), Firefox 129 (August 2024) und Safari 17.4 (März 2024) verfügbar. Stand 2026 liegt die globale Browser-Unterstützung bei über 90 Prozent. Für die verbleibenden Browser — hauptsächlich ältere Mobile-Safari-Versionen und Legacy-Edge — ist @starting-style ein perfekter Fall für Progressive Enhancement: Ohne @starting-style erscheint das Element einfach ohne Animation, was funktional korrekt ist. Die Kernfunktionalität (Popover öffnen, Dialog anzeigen) funktioniert in jedem Browser — nur die Animation fehlt in alten Browsern.
Für Projekte, die explizit ältere Browser unterstützen müssen, empfiehlt sich die @supports-Abfrage kombiniert mit einem Fallback. Man deklariert die Basisfunktionalität ohne Animation, und ergänzt in einem @supports (selector(:popover-open))-Block die Einblend-Animation mit @starting-style. Dieser Ansatz ist wartungsfreundlicher als ein JavaScript-Polyfill, der das Timing-Verhalten von @starting-style nachbilden müsste.
/* Progressive enhancement pattern for @starting-style */
/* Base styles — work in all browsers without animation */
[popover] {
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 1rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
/* Enhanced animation — only when @starting-style is supported */
@supports (selector(:popover-open)) {
[popover]:popover-open {
opacity: 1;
transform: translateY(0);
transition:
opacity 0.2s ease,
transform 0.2s ease,
display 0.2s allow-discrete,
overlay 0.2s allow-discrete;
}
/* Exit state (popover not open) */
[popover] {
opacity: 0;
transform: translateY(-8px);
}
@starting-style {
[popover]:popover-open {
/* Entry animation start point */
opacity: 0;
transform: translateY(-8px);
}
}
}
/* Staggered list items — entry animation with delay via custom property */
.card-list-item {
opacity: 1;
translate: 0;
transition: opacity 0.3s ease, translate 0.3s ease;
transition-delay: var(--stagger-delay, 0ms);
@starting-style {
opacity: 0;
translate: 0 12px;
}
}
Mironsoft
Modernes CSS, Frontend-Entwicklung und Hyvä-Theme-Expertise
Moderne CSS-Features produktiv einsetzen?
Wir implementieren @starting-style, View Transitions und moderne CSS-Animationen in euren Magento- und Hyvä-Projekten — sauber, zugänglich und mit korrekter Progressive-Enhancement-Strategie.
CSS-Audit
Analyse bestehender JS-Animationen auf CSS-Modernisierungspotenzial
Implementierung
@starting-style, View Transitions und Popover API in bestehende Projekte einbauen
Performance
Compositor-Thread-Animationen, CLS-Optimierung und Accessibility-konformes Animieren
10. Zusammenfassung
CSS @starting-style schließt eine der letzten großen Lücken im nativen CSS-Animationssystem. Die At-Rule ermöglicht echte Einblend-Animationen für Elemente, die aus display: none kommen oder neu ins DOM eingefügt werden — ohne JavaScript, ohne doppelte requestAnimationFrame-Aufrufe und ohne fragile Timing-Abhängigkeiten. In Kombination mit allow-discrete für diskrete Eigenschaften wie display und overlay entsteht ein vollständiges bidirektionales Animationssystem für Popovers, Dialoge, Dropdowns und dynamisch eingefügte Inhalte.
Die Progressive-Enhancement-Strategie ist klar: @starting-style ist in allen modernen Browsern (Chrome 117+, Firefox 129+, Safari 17.4+) verfügbar und deckt 2026 über 90 Prozent der Nutzer ab. In nicht unterstützten Browsern erscheint das Element ohne Animation — funktional einwandfrei. Wer heute noch JavaScript für Einblend-Animationen von Dialogen, Popovers oder dynamisch eingefügten Elementen verwendet, sollte den Wechsel zu @starting-style ernst in Betracht ziehen: weniger Code, bessere Performance, keine Race Conditions.
CSS @starting-style — Das Wichtigste auf einen Blick
Auslöser
Gilt für das erste Render-Frame: neu eingefügte DOM-Elemente oder Wechsel von display:none zu sichtbar.
allow-discrete
Schlüsselwort in transition erforderlich für diskrete Eigenschaften wie display und overlay.
Popover & Dialog
:popover-open und :modal als Zielzustand, @starting-style für den Einblende-Ausgangspunkt. Backdrop separat animieren.
Browser-Support
Chrome 117+, Firefox 129+, Safari 17.4+. Progressive Enhancement via @supports — fehlende Animation ist kein Funktionsfehler.