Es gibt einen Grund, warum JavaScript-Parallax immer leicht falsch aussieht – ein bisschen losgelöst, als würden die Ebenen einen halben Frame hinter der Seite schwimmen. Es ist kein nachlässiger Code. Es liegt daran, dass Scroll-Ereignisse JavaScript erst erreichen, nachdem der Browser das Scrollen bereits gerendert hat. Der Browser hat das schon einmal gelöst, als er klebende Header in position: sticky verwandelte. Scroll-driven Animations sind derselbe Schritt, auf alle scroll-gebundenen Effekte gleichzeitig angewendet.
Zehn Jahre lang bedeutete das Animieren einer Miniatur zu einem Hero-Bild eine einzige Technik: vor dem Element messen, danach messen, den Unterschied mit einem Transform vortäuschen und abspielen. FLIP. Jede Animationsbibliothek macht das so – und jede zahlt dafür mit JavaScript auf dem Hauptthread im denkbar schlechtesten Moment. Die View Transitions API verlagert diesen Trick in den Browser – und seit React 19.2 und Next.js 16 besteht der gesamte Vertrag aus einem einzigen Prop namens name.
Eine Liste mit tausend Kommentaren rendert langsam, also greifst du zu react-window – und tauschst dafür flüssiges Scrollen gegen Strg+F, den Seitendruck und teilweise die Navigation von Screenreadern ein, weil Elemente außerhalb des sichtbaren Bereichs physisch aufhören, im DOM zu existieren. content-visibility löst dasselbe Problem anders: Die Elemente bleiben im DOM, der Browser überspringt für sie nur die teure Layout- und Malarbeit, solange sie nicht in der Nähe des sichtbaren Bereichs sind – und kann sie, überraschenderweise, vorübergehend wieder sichtbar machen, wenn du in ihnen nach Text suchst.
Im Artikel über Barrierefreiheit war ein Modal mit display:none, das trotzdem noch im Accessibility-Baum existierte, ein Beispiel für einen Fehler, der beim Bau eines Modals von Grund auf mit Divs entsteht. Die eigentliche Frage lautet: Warum musste man es überhaupt von Grund auf bauen? Ich schaue mir an, was das native <dialog> und das popover-Attribut kostenlos mitbringen – Top Layer, Fokusfalle, Light Dismiss – und wo sich diese beiden Mechanismen so sehr unterscheiden, dass eine Verwechslung schnell zu einer unzugänglichen Oberfläche führt.
Die Utility-Klasse .mt-4 verliert gegen die Regel .card .card__header .card__title, obwohl sie logisch gewinnen sollte, weil sie später hinzugefügt wurde. Das ist kein Zufall – das ist Spezifität, die exakt das tut, wofür sie entworfen wurde. @layer führt eine völlig neue Achse zur Konfliktlösung ein, die vor der Spezifität greift, nicht neben ihr – und hat eine Falle, die so unintuitiv ist, dass selbst ein Teil der Dokumentation im Netz sie falsch darstellt.
Ein Formularfeld soll sich rot einfärben, wenn der Input darin ungültig ist. Klingt simpel – nur konnte CSS 25 Jahre lang ein Element nicht danach fragen, was in seinem Inneren passiert, sondern immer nur umgekehrt. Ich schaue mir an, wie :has() diese Richtung umkehrt, wie es sich von gewöhnlichen Kombinatoren unterscheidet und wo diese neue Macht die Performance wirklich kostet.