Entry-Animationen ganz ohne JavaScript
Ein Modal, das beim Öffnen einfach da ist, wirkt abrupt. Bisher war ein sanftes Einblenden beim ersten Rendern eine Domäne von JavaScript-Bibliotheken, die den Mount-Zeitpunkt abpassen mussten. Mit der CSS-Regel @starting-style übernimmt der Browser diese Aufgabe nativ, kombiniert mit den gewohnten Tailwind-Transition-Utilities.
Inhaltsverzeichnis
- 1. Das Problem mit Animationen beim ersten Rendern
- 2. @starting-style Grundlagen
- 3. Praxisbeispiel: Modal mit @starting-style einblenden
- 4. Kombination mit Tailwind Transition Utilities
- 5. Zusammenspiel mit display: none und allow-discrete
- 6. Vergleich zu bisherigen JavaScript-basierten Animate-on-Mount-Lösungen
- 7. Exit-Animationen und die Grenzen von @starting-style
- 8. Browser-Support-Stand von @starting-style
- 9. Best Practices für den produktiven Einsatz
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit Animationen beim ersten Rendern
CSS-Transitions animieren zuverlässig den Wechsel zwischen zwei Zuständen, etwa wenn eine Klasse per Klick hinzugefügt oder entfernt wird und sich dabei Opazität oder Transformation ändern. Genau das versagt aber beim allerersten Erscheinen eines Elements, weil eine Transition per Definition einen Ausgangswert und einen Zielwert braucht, das Element beim ersten Rendern aber direkt mit seinem finalen Zustand im DOM erscheint, ohne dass der Browser einen vorherigen Wert kennt, von dem aus animiert werden könnte.
Bisher wurde dieses Problem mit JavaScript umgangen, indem ein Element zunächst mit dem Startzustand ins DOM eingefügt wird, im nächsten Animationsframe die Zielklasse hinzugefügt wird und der Browser dazwischen die Transition auslöst. Diese Technik funktioniert, erfordert aber sorgfältiges Timing mit requestAnimationFrame, um Race Conditions zu vermeiden, bei denen der Browser beide Zustände im selben Frame zusammenfasst und die Animation komplett überspringt.
2. @starting-style Grundlagen
Die CSS-Regel @starting-style löst genau dieses Problem, indem sie explizit definiert, von welchem Zustand aus ein Element animieren soll, wenn es zum ersten Mal einen animierbaren Wert erhält, sei es beim ersten Einfügen ins DOM oder beim Wechsel von display: none zu einem sichtbaren Display-Wert. Innerhalb des Blocks werden dieselben Eigenschaften wie im Zielzustand deklariert, nur mit den Ausgangswerten, etwa opacity: 0 und einer verschobenen Transformation, während die eigentliche Zielregel die Endwerte trägt.
Der Browser erkennt automatisch, dass eine Transition definiert ist, entnimmt der starting-style-Regel den Ausgangswert und animiert von dort zum in der normalen Regel definierten Zielwert, sobald das Element sichtbar wird. Diese Logik funktioniert vollständig deklarativ und ohne jedes JavaScript, weil der Browser den kritischen ersten Frame selbst verwaltet, statt dass ein Skript den Timing-Trick mit zwei aufeinanderfolgenden Klassenzuweisungen nachbauen muss.
3. Praxisbeispiel: Modal mit @starting-style einblenden
Ein Modal-Dialog ist der klassische Anwendungsfall für @starting-style, weil es typischerweise mit dem HTML-dialog-Element oder über bedingtes Rendering in den DOM eingefügt wird und beim Öffnen sanft einblenden statt abrupt erscheinen soll. In Tailwind lässt sich dafür eine kleine, wiederverwendbare Utility-Kombination definieren, die opacity und scale über eine Transition animiert und die Ausgangswerte über @starting-style festlegt.
Im folgenden Beispiel öffnet sich ein Modal aus einer leicht verkleinerten, transparenten Ausgangsposition heraus zur vollen Größe und Deckkraft, gesteuert allein über CSS. Wichtig ist die Kombination mit transition-discrete beziehungsweise dem CSS-Wert allow-discrete, damit auch der Sprung von display: none zu einem sichtbaren Wert sauber animiert wird, statt dass der Browser die Sichtbarkeitsänderung sofort ohne Übergang vollzieht.
<dialog class="m-auto rounded-xl bg-white p-6 shadow-xl backdrop:bg-black/50
opacity-100 scale-100 transition-all duration-300
starting:opacity-0 starting:scale-95
open:opacity-100 open:scale-100
[transition-behavior:allow-discrete]">
<h2 class="text-lg font-semibold text-gray-900">Bestellung bestätigen</h2>
<p class="mt-2 text-sm text-gray-600">
Möchtest du die Bestellung wirklich abschicken?
</p>
<div class="mt-4 flex justify-end gap-2">
<button class="rounded-md border px-3 py-1.5 text-sm">Abbrechen</button>
<button class="rounded-md bg-blue-600 px-3 py-1.5 text-sm text-white">Bestätigen</button>
</div>
</dialog>
4. Kombination mit Tailwind Transition Utilities
Tailwind stellt bereits ein ausgereiftes Set an Transition-Utilities bereit, etwa transition-all, duration-300 oder ease-out, die sich unverändert mit @starting-style kombinieren lassen, weil die Ausgangswerte in der starting-style-Regel lediglich definieren, wovon aus animiert wird, während Dauer, Timing-Funktion und die zu animierenden Eigenschaften weiterhin über die normalen Transition-Utilities gesteuert werden. Es ist deshalb keine separate Animations-API nötig, das bestehende Transition-System von Tailwind bleibt vollständig gültig.
In neueren Tailwind-Versionen mit CSS-Variablen-basiertem Theming lässt sich außerdem ein eigener Utility-Präfix wie starting: als Variante definieren, der intern die @starting-style-Regel erzeugt, sodass Entwickler den Ausgangszustand direkt als Utility-Klasse im Markup notieren können, statt eine separate CSS-Datei mit der @starting-style-Regel von Hand zu pflegen. Das hält den deklarativen Charakter von Utility-First auch für diese neue CSS-Funktion konsequent bei.
5. Zusammenspiel mit display: none und allow-discrete
Ein Sonderfall betrifft Eigenschaften, die selbst nicht kontinuierlich animierbar sind, allen voran display, das entweder none oder ein sichtbarer Wert ist, ohne Zwischenwerte. Normalerweise überspringt der Browser hier jede Transition, weil ein diskreter Eigenschaftswechsel sofort erfolgt, ohne dass es eine sinnvolle Zwischenstufe zwischen none und block gäbe, wodurch eine gleichzeitig laufende Opacity-Transition ins Leere liefe, sobald das Element komplett aus dem Layout verschwindet.
Der CSS-Wert transition-behavior: allow-discrete löst dieses Problem, indem er dem Browser erlaubt, den display-Wechsel bis zum Ende der übrigen Transition hinauszuzögern, statt ihn sofort auszuführen. In Kombination mit @starting-style ermöglicht das ein vollständig animiertes Ein- und Ausblenden eines Elements inklusive des display-Wechsels, was vorher praktisch immer JavaScript erforderte, weil reines CSS die Reihenfolge von display-Wechsel und Opacity-Animation nicht koordinieren konnte.
6. Vergleich zu bisherigen JavaScript-basierten Animate-on-Mount-Lösungen
Bibliotheken wie Framer Motion oder Alpine.js-Transition-Direktiven lösen das Mount-Animation-Problem bislang, indem sie den Startzustand per Skript setzen, im nächsten Frame den Zielzustand anwenden und dazwischen die eigentliche CSS-Transition auslösen. Das funktioniert zuverlässig, kostet aber zusätzliches JavaScript im Bundle, zusätzliche Rechenzeit für das Timing der Frames und macht die Animation von der korrekten Ausführung des Skripts abhängig, was bei langsamen Geräten oder blockiertem Hauptthread zu ausbleibenden Animationen führen kann.
@starting-style verlagert diese Logik vollständig in die CSS-Rendering-Pipeline des Browsers, wodurch die Animation garantiert im richtigen Frame startet, unabhängig davon, wie ausgelastet der JavaScript-Hauptthread gerade ist. Für Alpine.js-lastige Hyvä-Projekte bedeutet das, dass die x-transition-Direktive für viele einfache Mount-Animationen künftig durch reines CSS mit @starting-style ersetzt werden kann, was den JavaScript-Payload reduziert, ohne auf das visuelle Ergebnis verzichten zu müssen.
7. Exit-Animationen und die Grenzen von @starting-style
@starting-style behandelt ausschließlich das Erscheinen eines Elements, nicht sein Verschwinden, weil die Regel per Definition nur beim Übergang von einem nicht gerenderten oder nicht sichtbaren Zustand zu einem sichtbaren Zustand greift. Für das Ausblenden eines Elements, etwa ein Modal, das beim Schließen sanft verblasst, reicht eine normale Transition auf die Zielwerte in Kombination mit allow-discrete für den display-Wechsel am Ende, ganz ohne eigene @starting-style-Regel für diese Richtung.
Eine echte Grenze ergibt sich bei Elementen, die zwischen mehreren sichtbaren Zuständen wechseln, ohne jemals komplett aus dem DOM zu verschwinden, etwa ein Akkordeon, das zwischen offen und geschlossen wechselt, aber nie ganz unsichtbar wird, weil hier @starting-style nicht greift und weiterhin normale Transitions zwischen zwei definierten Zuständen die richtige Wahl bleiben. @starting-style ist gezielt für das erste Erscheinen gedacht, nicht als generelle Ersatzlösung für jede Art von Zustandsübergang.
8. Browser-Support-Stand von @starting-style
Chrome und Edge unterstützen @starting-style bereits seit Version 117 auf Basis von Blink, Safari hat die Unterstützung mit Version 17.5 nachgezogen, und Firefox hat die Implementierung ebenfalls umgesetzt, sodass die Regel inzwischen in allen drei großen Engine-Familien ankommt. Wie bei anderen neueren CSS-Features gilt auch hier, dass der genaue Support-Stand vor dem produktiven Einsatz auf einer aktuellen Referenzseite geprüft werden sollte, weil sich Versionsnummern und Rollout-Zeitpunkte je nach Zielgruppe des Projekts unterschiedlich stark auswirken.
Da @starting-style rein additiv wirkt und beim Fehlen einfach ignoriert wird, führt fehlende Unterstützung nicht zu einem kaputten Layout, sondern lediglich dazu, dass das Element ohne Einblend-Animation direkt im Zielzustand erscheint, was dem bisherigen Standardverhalten entspricht. Das macht @starting-style zu einem risikoarmen progressiven Enhancement, das sich schon vor vollständiger Browserabdeckung produktiv einsetzen lässt.
9. Best Practices für den produktiven Einsatz
Eine wichtige Praxisregel ist, die @starting-style-Werte bewusst dezent zu wählen, etwa eine leichte Skalierung von 0.95 auf 1 statt einer dramatischen Skalierung von 0 auf 1, weil zu starke Einblend-Effekte schnell unruhig wirken und die Wahrnehmung des eigentlichen Inhalts stören können. Kurze Transition-Dauern zwischen 150 und 300 Millisekunden mit einer sanften Easing-Funktion wie ease-out liefern in der Praxis die besten Ergebnisse für Modals, Tooltips und Dropdown-Menüs.
Außerdem sollte @starting-style nicht wahllos auf jedes neu gerenderte Element angewendet werden, weil zu viele gleichzeitige Einblend-Animationen, etwa in einer langen Liste neu geladener Karten, optisch überladen wirken und die Nutzerführung eher stören als unterstützen. Sinnvoll eingesetzt ist die Regel dort, wo ein einzelnes, klar abgegrenztes Element wie ein Modal, ein Toast oder ein Dropdown-Menü Aufmerksamkeit verdient und ein sanftes Erscheinen die Wahrnehmung des Kontexts unterstützt, statt sie zu zerstreuen.
| Aspekt | Ohne @starting-style | Mit @starting-style | Praxisrelevanz |
|---|---|---|---|
| Ausgangswert beim Mount | Nicht definierbar, Element erscheint sofort im Zielzustand | Explizit über den @starting-style-Block definierbar | Ermöglicht Fade-in ohne JavaScript |
| display: none Übergang | Springt sofort, keine Transition möglich | Mit allow-discrete verzögert bis Transitionsende | Nötig für Modals und Dropdowns |
| JavaScript-Bedarf | Skript nötig für Zwei-Frame-Trick | Kein Skript nötig, rein deklarativ | Reduziert Bundle-Größe und Timing-Risiken |
| Browser-Support | Nicht relevant, da kein neues Feature | Chrome/Edge, Safari 17.5+, aktuelles Firefox | Progressive Enhancement ohne Fallback-Code |
Mironsoft
Tailwind-CSS-Architektur, Design-Systeme und Performance
Tailwind-Frontends, die trotz tausender Utility-Klassen wartbar bleiben?
Wir prüfen bestehende Tailwind-Projekte auf aufgeblähte Klassenlisten, inkonsistente Design-Tokens und ungenutzte CSS-Reste und bauen daraus ein Design-System, das sich sauber skaliert statt mit jeder Komponente unübersichtlicher zu werden.
Design-System-Review
Tokens, Spacing-Skala und Komponentenkonsistenz auf Wartbarkeit prüfen.
Performance-Optimierung
CSS-Bundle-Größe, Purge-Konfiguration und Ladezeiten systematisch reduzieren.
Component-Architektur
Wiederverwendbare, gut strukturierte Komponenten statt Klassenlisten-Wildwuchs aufbauen.
10. Zusammenfassung
@starting-style: Das Wichtigste auf einen Blick
@starting-style
Definiert den Ausgangswert eines Elements beim ersten Rendern für eine Transition.
allow-discrete
Verzögert den display-Wechsel bis zum Ende der Transition, nötig für echtes Ein-/Ausblenden.
Kein JavaScript
Ersetzt den Zwei-Frame-Trick mit requestAnimationFrame durch reines, deklaratives CSS.
Einsatzort
Ideal für Modals, Toasts und Dropdowns, nicht für generelle Zustandsübergänge.