Der Hover-Lift: Wie eine 20-Zeichen-Tailwind-Geste zum KI-Fingerabdruck wurde
`hover:-translate-y-1 hover:shadow-xl` klebt auf jeder KI-gebauten Karte, egal ob sie klickbar ist oder nicht. Warum die immer gleiche 4-Pixel-Anhebung nach Maschine aussieht und welche Hover-Gesten stattdessen etwas bedeuten.
Mach die Dev-Konsole auf den letzten sechs KI-gebauten Landingpages auf, die dir untergekommen sind. Fahr mit der Maus über eine Pricing-Karte. Sie hebt sich an. Über eine Feature-Karte. Sie hebt sich um genau denselben Betrag an. Über ein Testimonial, einen Blog-Teaser, ein Foto aus dem Teambereich, das Logo im Footer, das ganz sicher kein Button sein sollte – und sie alle steigen um exakt 4 Pixel und bekommen einen Schatten. Die Bewegung ist identisch, weil das Markup identisch ist:
<div class="rounded-2xl border bg-card p-6 shadow-sm
transition-all hover:-translate-y-1 hover:shadow-xl">hover:-translate-y-1 hover:shadow-xl. Zwanzig Zeichen Tailwind. Es ist die meistausgelieferte Mikrointeraktion der KI-Build-Ära, und sobald du gelernt hast, sie zu sehen, kriegst du sie nicht mehr aus dem Kopf. Sie ist das Design-Pendant zur fade-in-up-Scroll-Animation – ein Default, der so gleichförmig angewendet wird, dass er aufhört, eine Entscheidung zu sein, und stattdessen zum Fingerabdruck wird.
Was die zwei Klassen tatsächlich tun
-translate-y-1 ist transform: translateY(-0.25rem) – eine Verschiebung um 4px nach oben. shadow-xl tauscht Tailwinds Standard-box-shadow gegen einen größeren, weicheren aus:
/* shadow-sm (Ruhezustand) */
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
/* shadow-xl (Hover) */
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);Und transition-all verbindet die beiden über 150ms mit Tailwinds Standard-Easing cubic-bezier(0.4, 0, 0.2, 1). Die Karte schwebt nach oben, wirft einen tieferen Schatten, und das Gehirn liest: "Das hier lässt sich anheben, du darfst klicken."
Für sich genommen ist das in Ordnung. Es ist eine echte, lesbare Affordanz mit Wurzeln, die älter sind als Tailwind – Material Designs Elevation-System hat 2014 mit z-depth genau dasselbe gemacht. Das Problem ist nicht die Geste. Das Problem ist, dass sie inzwischen auf *alles* angewendet wird, mit *null* Variation in Distanz, Easing oder Schatten, auf Elementen, die nicht einmal interaktiv sind.
Warum Generatoren reflexartig danach greifen
Bitte Cursor, v0, Lovable oder Bolt um "ein Feature-Grid", und du bekommst den Hover-Lift ungefragt dazu. Niemand hat "füge eine Hover-Animation hinzu" getippt. Er kommt als beiläufiger Default mit, und dafür gibt es drei Gründe.
Er ist das statistische Zentrum der Trainingsdaten. Jedes shadcn/ui-Card-Beispiel, jeder Tailwind-UI-Marketing-Block, jedes "100 best landing pages"-Repo, das gescrapt wurde, nutzt irgendeine Spielart von Lift-on-Hover. Wenn das Modell vorhersagt, "was nach rounded-2xl border kommt", ist hover:-translate-y-1 hover:shadow-xl die wahrscheinlichste Vervollständigung. Es ist Regression zum Mittelwert, verkleidet als Design. Die shadcn-Monokultur endet nicht bei der Card-Komponente – sie reicht hinein in das Verhalten dieser Karte.
Er ist ein kostenloses "Politur"-Signal. LLMs sind darauf getrimmt, hilfreich zu wirken. Eine statische Karte sieht unfertig aus; eine Karte, die reagiert, sieht "interaktiv" und "modern" aus. Das Modell greift nach der billigsten Geste, die nach Aufwand aussieht. transition-all hover:-translate-y-1 ist das Motion-Design-Äquivalent dazu, einen Teller mit einem Zweig Petersilie zu garnieren – es kostet nichts und signalisiert "da hat jemand drüber nachgedacht", obwohl es niemand getan hat.
transition-all ist das faule Bündeln. Ein sorgfältiger Entwickler animiert nur die Eigenschaften, die sich ändern: transition-[transform,box-shadow]. Der Generator schreibt transition-all, weil er nicht weiß – oder sich nicht die Mühe macht zu überlegen – welche Eigenschaften animiert werden. Also animiert er das Universum, worauf wir noch zurückkommen, denn das hat einen echten Preis.
Warum eine gleichförmige Anhebung nach Maschine aussieht
Von Menschen gestaltete Hover-States haben *Absicht* – sie unterscheiden sich danach, wofür das Element *da ist*. Ein primäres CTA füllt sich vielleicht von links nach rechts. Eine Pricing-Karte hebt ihren Rahmen auf die Akzentfarbe und stupst eine Zahl an. Ein Thumbnail zoomt das Bild innerhalb eines festen Rahmens. Ein Nav-Link schiebt eine Unterstreichung hinein. Jede Geste beantwortet die Frage: "Was bedeutet es, mit diesem Ding zu interagieren?"
Der KI-Default beantwortet gar nichts. Er legt dieselbe 4px-Anhebung auf eine $0/forever-Karte und eine $499/year-Karte, auf einen klickbaren Blog-Teaser und einen nicht klickbaren Stat-Block, auf einen Button und ein dekoratives Icon. Wenn *jedes* Element *ein* Hover-Verhalten teilt, hört das Verhalten auf zu kommunizieren. Ein Hover-State, der "alles" bedeutet, bedeutet nichts – er ist nur Textur.
Das ist genau das Verräter-Detail, das in die CSS lesen, um eine Seite auf KI zu prüfen beschrieben wird: DevTools auf, über drei strukturell verschiedene Elemente fahren und prüfen, ob die :hover-Regel Byte für Byte identisch ist. Auf einer von Menschen gebauten Seite hovern ein CTA und eine Blog-Karte unterschiedlich, weil eine Person entschieden hat, dass sie das sollten. Bei einem KI-Build findest du auf allen dreien dasselbe translateY(-0.25rem) und denselben shadow-xl. Die Gleichförmigkeit *ist* die Signatur – dieselbe Energie wie der Blau-zu-Lila-Verlauf, der unabhängig von der Marke auf jedem Hero auftaucht.
Im Easing versteckt sich ein zweites Verräter-Detail. Das Standard-cubic-bezier(0.4, 0, 0.2, 1) über 150ms ist für ein einzelnes Element in Ordnung. Aber wenn zwölf Karten in einem Grid alle mit der identischen Kurve und Dauer hochsteigen, erzeugt das Drüberfahren mit der Maus eine mechanische Welle – jede Karte poppt hoch und fällt zurück mit metronomischer Gleichförmigkeit. Gestaltete Bewegung hat Rhythmus und Variation; das hier hat keine. Es ist der räumliche Cousin der gleichförmigen fade-in-up-Scroll-Choreografie, bei der jeder Abschnitt mit demselben Translate-und-Fade auf demselben Timing erscheint.
Die Perf- und Jank-Kosten, die niemand misst
Hier hört der Default auf, bloß langweilig zu sein, und fängt an, aktiv schlecht zu sein.
transition-all animiert box-shadow, und das ist teuer. Das Animieren von box-shadow zwingt den Browser, das Element in jedem Frame neu zu zeichnen – es kann nicht so an den GPU-Compositor ausgelagert werden wie transform und opacity. Bei einer einzelnen Karte merkst du es nicht. Bei einem 12-Karten-Grid, über das der Nutzer mit der Maus hinwegfegt, löst du in schneller Folge zwölf Repaints auf einer großen, weichgezeichneten Schattenfläche aus. Auf einem Mittelklasse-Android-Handy oder einem billigen Laptop verpufft genau dort das Frame-Budget, und die Anhebung fängt an zu ruckeln.
transition-all animiert außerdem Dinge, die du gar nicht gemeint hast. Weil es all ist, wird jede Eigenschaft, die sich zufällig ändert – eine Farbverschiebung vom Parent, ein Layout-Schubs, ein Re-Render, der eine Klasse austauscht – ebenfalls animiert, manchmal mit einer sichtbaren Verzögerung dort, wo du eine sofortige Änderung wolltest. Es ist eine Fußangel, gerade weil es undifferenziert ist.
Die Anhebung kann Layout-Thrash auslösen, wenn die Karte Nachbarn hat. translateY selbst ist compositor-freundlich und löst kein Reflow aus. Aber Generatoren paaren die Anhebung häufig mit einem hover:scale-105, und eine Karte innerhalb eines engen CSS-Grids zu skalieren kann dazu führen, dass sie ihre Geschwister überlappt oder visuell mit ihnen kollidiert, und in manchen Layouts den umliegenden Inhalt anstößt. Die "Politur"-Geste erzeugt einen Glitch.
Der Fix für die Perf-Hälfte ist eine Zeile:
<!-- KI-Default -->
<div class="transition-all hover:-translate-y-1 hover:shadow-xl">
<!-- Ehrliche Version: Eigenschaften benennen, den Schatten-Sprung kürzen -->
<div class="transition-[transform,box-shadow] duration-200 ease-out
hover:-translate-y-0.5 hover:shadow-lg
will-change-transform">Benenne die Eigenschaften, damit du nicht das Universum animierst. Gib dem Compositor mit will-change-transform einen Hinweis, wenn die Anhebung die Hauptbewegung ist. Und kürze den Schatten-Delta – von shadow-sm zu shadow-lg statt zu shadow-xl zeichnet eine kleinere Fläche neu und sieht weniger so aus, als würde die Karte in Panik geraten.
Hover-Alternativen, die tatsächlich etwas bedeuten
Das Ziel ist nicht, Hover-States zu verbieten. Es ist, jeden einzelnen *eine Frage beantworten* zu lassen über das Element, auf dem er sitzt. Hier sind Gesten mit Absicht, nach Elementtyp.
Ein primäres CTA: füllen, nicht schweben
Ein Button ist das eine Ding auf der Seite, das du am meisten geklickt haben willst. Gib ihm eine gerichtete Füllung, damit der Hover "das ist die Aktion" verstärkt und nicht "das ist eine Karte, die zufällig ein Button ist".
.cta {
background: #0a0a0a;
background-image: linear-gradient(90deg, #f43f5e 0%, #f43f5e 100%);
background-size: 0% 100%;
background-repeat: no-repeat;
transition: background-size 220ms ease-out;
}
.cta:hover { background-size: 100% 100%; }Der Akzent (#f43f5e, ein echtes Rosé, kein Tailwind-Blau – siehe einen Akzent wählen, der nicht der Default ist) fegt von links herein. Er ist gerichtet, er ist spezifisch für einen Button, und er ist GPU-günstig, weil background-size hier keinen weichgezeichneten Schatten neu zeichnet.
Eine Pricing-Karte: Zustand ändern, nicht Höhe
Die Aufgabe einer Pricing-Karte ist der Vergleich. Der interessante Hover ist nicht "aufsteigen" – sondern "festlegen". Heb den Rahmen auf die Akzentfarbe und das innere CTA an, während die Karte selbst stehen bleibt:
<div class="group rounded-2xl border-2 border-zinc-200
transition-colors duration-200 hover:border-rose-500">
<span class="text-4xl font-semibold tabular-nums">$24</span>
<button class="mt-6 w-full rounded-lg bg-zinc-900 py-2
transition-transform group-hover:-translate-y-0.5">
Start
</button>
</div>Die Karte bleibt an Ort und Stelle; der *Rahmen* und der *innere Button* reagieren. Das liest sich als "du fokussierst dich auf diesen Plan", und genau dafür ist eine Pricing-Karte da. Nutze group-hover, damit die Geste das bedeutungstragende Kind anvisiert, nicht die ganze Platte.
Ein Content-Thumbnail: innerhalb des Rahmens zoomen
Für einen Blog-Teaser oder ein Portfolio-Bild sagt dir die Anhebung nichts. Zoome das Bild *innerhalb* eines Rahmens mit festem Overflow, sodass die Grundfläche der Karte sich nie bewegt und nichts reflowt:
<a class="block overflow-hidden rounded-xl">
<img class="aspect-video w-full object-cover
transition-transform duration-500 ease-out
hover:scale-105" />
</a>Der Rahmen ist stabil, das Bild atmet, die Bewegung ist langsam (500ms) und filmisch statt ein zappeliger 150ms-Pop. Langsameres Easing auf Bildern, schnelleres auf Buttons – allein diese Variation durchbricht die maschinengestanzte Gleichförmigkeit.
Ein Nav-Link: eine Unterstreichung, die sich zeichnet
.nav-link {
background-image: linear-gradient(currentColor, currentColor);
background-size: 0% 1px;
background-position: 0 100%;
background-repeat: no-repeat;
transition: background-size 180ms ease;
}
.nav-link:hover { background-size: 100% 1px; }Eine Unterstreichung, die sich von links nach rechts zeichnet. Es ist ein Link, er verhält sich wie ein Link, und er teilt keine DNA mit der Karten-Anhebung.
Der nicht interaktive Stat-Block: gar kein Hover
Die am meisten unterschätzte Alternative. Wenn ein Stat-Block, ein Logo oder eine Section-Überschrift nicht klickbar ist, gib ihm *keinen* Hover-State. Das Menschlichste, was du tun kannst, ist, nicht interaktive Elemente reglos zu lassen. Das Verräterische am KI-Default ist zum Teil, dass er dekorative Dinge anhebbar erscheinen lässt – Zurückhaltung dreht den Fingerabdruck augenblicklich um.
Das Ein-Zeilen-Audit und die Heilung
Um zu prüfen, ob deine eigene Seite das Verräter-Detail hat, füg das in die Konsole ein und hover herum:
[...document.querySelectorAll('*')]
.map(el => getComputedStyle(el, ':hover')) // illustrativ
// Praktisch: in den DevTools über 3 verschiedene Elemente fahren,
// die :hover-Regel inspizieren, transform + box-shadow vergleichen.Wenn ein Button, eine Karte und ein Nicht-Link alle zu translateY(-0.25rem) + shadow-xl aufgelöst werden, hast du den Default ausgeliefert. Die Heilung ist nicht mehr Animation – sie ist *differenzierte* Animation plus bewusste Reglosigkeit. Drei oder vier verschiedene Hover-Gesten, jede abgestimmt auf das, was ihr Element tut, plus ein entschiedenes "kein Hover" auf allem Dekorativen. Das ist der Unterschied zwischen Bewegung, die kommuniziert, und Bewegung, die nur das Modell ist, das den Teller garniert.
Der Hover-Lift ist nicht böse. Er ist eine völlig vernünftige Affordanz, die durch schiere Wiederholung zum Fingerabdruck wurde – auf dieselbe Weise, wie jede KI-Seite nach derselben Handvoll Moves greift. Der Fix ist derselbe wie immer: hör auf, die wahrscheinlichste Vervollständigung als Design-Entscheidung zu akzeptieren. Hover-States sind billig. Hover-States mit *Absicht* sind die ganze Arbeit.
SHIP CODE THAT LOOKS INTENTIONAL
Scan your frontend for AI patterns. Generate a unique design system. Stop shipping the same blue gradient as everyone else.