Le hover qui soulève de 4 pixels : la micro-interaction signature des sites générés par IA
Toutes les cartes montent de 4 pixels au survol et gagnent une ombre, qu'elles soient cliquables ou non. Voici pourquoi ce réflexe trahit une page générée par IA, et comment lui substituer des hovers qui veulent dire quelque chose.
Ouvrez la console sur les six dernières landing pages générées par IA que vous avez croisées. Survolez une carte de tarif : elle monte. Survolez une carte de fonctionnalité : elle monte de la même hauteur. Survolez un témoignage, un teaser d'article, la photo d'un membre de l'équipe, le logo dans le footer qui ne devrait surtout pas être un bouton, et tous montent d'exactement 4 pixels en se parant d'une ombre. Le mouvement est identique parce que le markup est identique :
<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. Vingt caractères de Tailwind. C'est la micro-interaction la plus expédiée de l'ère du build par IA, et le jour où vous apprenez à la repérer, vous ne pouvez plus la quitter des yeux. C'est l'équivalent design de l'animation au scroll fade-in-up : un réglage par défaut appliqué si uniformément qu'il cesse d'être un choix pour devenir une empreinte digitale.
Ce que font vraiment ces deux classes
-translate-y-1, c'est transform: translateY(-0.25rem), soit un décalage de 4px vers le haut. shadow-xl remplace la box-shadow par défaut de Tailwind par une ombre plus large et plus douce :
/* shadow-sm (au repos) */
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
/* shadow-xl (au survol) */
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);Et transition-all relie le tout sur 150ms avec la courbe d'accélération par défaut de Tailwind, cubic-bezier(0.4, 0, 0.2, 1). La carte flotte, projette une ombre plus profonde, et le cerveau lit « cet objet se soulève, tu peux cliquer dessus ».
Pris isolément, c'est très bien. C'est un affordance réel, lisible, dont les racines précèdent largement Tailwind : le système d'élévation de Material Design faisait exactement la même chose avec z-depth en 2014. Le problème n'est pas le geste. Le problème, c'est qu'il s'applique désormais à *tout*, avec *zéro* variation de distance, de courbe ou d'ombre, sur des éléments qui ne sont même pas interactifs.
Pourquoi les générateurs y reviennent par réflexe
Demandez à Cursor, v0, Lovable ou Bolt « une grille de fonctionnalités » et vous récupérez le hover-lift sans l'avoir demandé. Personne n'a tapé « ajoute une animation au survol ». Il débarque comme un réglage ambiant, et il y a trois raisons à cela.
C'est le centre statistique des données d'entraînement. Chaque exemple de carte shadcn/ui, chaque bloc marketing de Tailwind UI, chaque repo « 100 plus belles landing pages » qui s'est fait scraper utilise une variante du soulèvement au survol. Quand le modèle prédit « ce qui vient après rounded-2xl border », hover:-translate-y-1 hover:shadow-xl est la complétion la plus probable. C'est un retour à la moyenne déguisé en parti pris design. La monoculture shadcn ne s'arrête pas au composant Card : elle déborde sur la façon dont cette carte se comporte.
C'est un signal de « finition » gratuit. Les LLM sont calibrés pour paraître serviables. Une carte statique a l'air inachevée ; une carte qui réagit a l'air « interactive » et « moderne ». Le modèle attrape le geste le moins cher qui se lit comme un effort. transition-all hover:-translate-y-1, c'est l'équivalent en motion design de la brindille de persil posée sur l'assiette : ça ne coûte rien et ça signale « quelqu'un y a réfléchi », alors même que personne n'y a réfléchi.
transition-all, c'est le regroupement paresseux. Un dev soigneux ne transitionne que les propriétés qui changent : transition-[transform,box-shadow]. Le générateur écrit transition-all parce qu'il ne sait pas, ou ne prend pas la peine de raisonner sur, quelles propriétés vont s'animer. Du coup il anime l'univers entier, et on y reviendra, parce que ça a un vrai coût.
Pourquoi un soulèvement uniforme se lit comme du fait-machine
Les états de survol conçus par des humains ont une *intention* : ils diffèrent selon ce à quoi sert l'élément. Un CTA principal pourrait se remplir de gauche à droite. Une carte de tarif pourrait passer sa bordure à la couleur d'accent et incrémenter un chiffre. Une vignette pourrait zoomer l'image à l'intérieur d'un cadre fixe. Un lien de navigation pourrait faire glisser un soulignement. Chaque geste répond à la question « qu'est-ce que ça signifie d'interagir avec cet objet ? ».
Le réglage par défaut de l'IA ne répond à rien. Il applique le même soulèvement de 4px à une carte $0/forever et à une carte $499/year, à un teaser d'article cliquable et à un bloc de statistique non cliquable, à un bouton et à une icône décorative. Quand *chaque* élément partage *un seul* comportement de survol, ce comportement cesse de communiquer. Un hover qui veut dire « tout » ne veut plus rien dire : ce n'est plus que de la texture.
C'est exactement le révélateur décrit dans lire le CSS pour auditer un site et y débusquer l'IA : ouvrez les DevTools, survolez trois éléments structurellement différents, et vérifiez si la règle :hover est rigoureusement identique octet pour octet. Sur un site fait par un humain, un CTA et une carte d'article se survolent différemment parce qu'une personne a décidé qu'ils le devaient. Sur un build IA, vous trouverez le même translateY(-0.25rem) et la même shadow-xl sur les trois. L'uniformité *est* la signature, même énergie que le dégradé bleu-vers-violet qui surgit sur chaque hero quelle que soit la marque.
Il y a un second révélateur tapi dans la courbe d'accélération. La cubic-bezier(0.4, 0, 0.2, 1) par défaut sur 150ms convient très bien pour un seul élément. Mais quand douze cartes d'une grille se soulèvent toutes avec la courbe et la durée identiques, balayer la grille à la souris produit une ondulation mécanique : chaque carte qui jaillit et retombe avec une régularité de métronome. Le mouvement pensé a du rythme et de la variation ; celui-ci n'en a aucun. C'est le cousin spatial de la chorégraphie de scroll fade-in-up uniforme où chaque section entre avec le même translate-and-fade sur le même timing.
Le coût en perfs et en saccades que personne ne mesure
C'est ici que le réglage par défaut cesse d'être simplement ennuyeux pour devenir activement néfaste.
transition-all anime box-shadow, ce qui coûte cher. Animer box-shadow force le navigateur à repeindre l'élément à chaque frame : impossible de déléguer ça au compositeur GPU comme on le fait avec transform et opacity. Sur une seule carte, vous ne verrez rien. Sur une grille de 12 cartes où l'utilisateur balaie la souris sur toutes, vous déclenchez des repaints sur une vaste zone d'ombre floutée douze fois coup sur coup. Sur un Android de milieu de gamme ou un laptop bon marché, c'est là que part le budget de frame et c'est là que le soulèvement commence à hoqueter.
transition-all anime aussi des choses que vous ne vouliez pas. Comme c'est all, toute propriété qui change par accident, un changement de couleur venu d'un parent, un décalage de layout, un re-render qui permute une classe, se retrouve animée elle aussi, produisant parfois un lag visible là où vous vouliez un changement instantané. C'est un piège précisément parce que c'est indiscriminé.
Le soulèvement peut provoquer du layout thrash si la carte a des voisines. translateY en soi est compatible avec le compositeur et ne déclenche pas de reflow. Mais les générateurs associent fréquemment le soulèvement à un hover:scale-105, et agrandir une carte dans une CSS grid serrée peut la faire chevaucher ou entrer visuellement en collision avec ses voisines, et sur certains layouts bousculer le contenu alentour. Le geste de « finition » crée un bug d'affichage.
Le correctif pour la moitié perfs tient en une ligne :
<!-- Réglage par défaut de l'IA -->
<div class="transition-all hover:-translate-y-1 hover:shadow-xl">
<!-- Version honnête : on nomme les propriétés, on raccourcit le saut d'ombre -->
<div class="transition-[transform,box-shadow] duration-200 ease-out
hover:-translate-y-0.5 hover:shadow-lg
will-change-transform">Nommez les propriétés pour ne pas animer l'univers entier. Donnez un indice au compositeur avec will-change-transform si le soulèvement est le mouvement principal. Et réduisez le delta d'ombre : passer de shadow-sm à shadow-lg plutôt qu'à shadow-xl repeint une zone plus petite et donne moins l'impression que la carte panique.
Des alternatives au hover qui veulent dire quelque chose
Le but n'est pas de bannir les états de survol. C'est de faire en sorte que chacun *réponde à une question* sur l'élément qui le porte. Voici des gestes dotés d'intention, par type d'élément.
Un CTA principal : remplir, pas flotter
Un bouton est la seule chose sur la page que vous voulez vraiment voir cliquée. Donnez-lui un remplissage directionnel pour que le survol renforce « ceci est l'action », et non « ceci est une carte qui se trouve être un bouton ».
.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%; }L'accent (#f43f5e, un vrai rose, pas le bleu Tailwind, voir choisir une couleur d'accent qui n'est pas celle par défaut) balaie depuis la gauche. C'est directionnel, c'est spécifique à un bouton, et c'est peu coûteux pour le GPU parce qu'ici background-size ne repeint pas une ombre floutée.
Une carte de tarif : changer d'état, pas d'altitude
Le rôle d'une carte de tarif, c'est la comparaison. Le hover intéressant n'est pas « monter », c'est « s'engager ». Faites passer la bordure à l'accent et soulevez le CTA à l'intérieur, en laissant la carte elle-même ancrée :
<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>La carte reste en place ; c'est la *bordure* et le *bouton interne* qui répondent. Ça se lit comme « tu es en train de te concentrer sur cette formule », ce qui est précisément l'objet d'une carte de tarif. Utilisez group-hover pour que le geste vise l'enfant porteur de sens, pas la dalle entière.
Une vignette de contenu : zoomer dans le cadre
Pour un teaser d'article ou une image de portfolio, le soulèvement ne vous apprend rien. Zoomez l'image *à l'intérieur* d'un cadre à débordement fixe pour que l'emprise de la carte ne bouge jamais et que rien ne reflow :
<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>Le cadre est stable, l'image respire, le mouvement est lent (500ms) et cinématographique au lieu d'un pop nerveux de 150ms. Courbe plus lente sur les images, plus rapide sur les boutons : cette variation à elle seule brise l'uniformité estampillée machine.
Un lien de navigation : un soulignement qui se trace
.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; }Un soulignement qui se trace de gauche à droite. C'est un lien, il se comporte comme un lien, et il ne partage aucun ADN avec le soulèvement des cartes.
Le bloc de statistique non interactif : aucun hover
L'alternative la plus sous-estimée. Si un bloc de statistique, un logo ou un titre de section n'est pas cliquable, ne lui donnez *aucun* état de survol. La chose la plus humaine que vous puissiez faire, c'est de laisser inertes les éléments non interactifs. Le révélateur du réglage par défaut de l'IA tient en partie à ce qu'il fait paraître soulevables des choses décoratives ; la retenue inverse l'empreinte digitale instantanément.
L'audit en une ligne, et le remède
Pour vérifier si votre propre site porte le révélateur, collez ceci dans la console et survolez un peu partout :
[...document.querySelectorAll('*')]
.map(el => getComputedStyle(el, ':hover')) // illustratif
// En pratique : survolez 3 éléments différents dans les DevTools,
// inspectez la règle :hover, comparez transform + box-shadow.Si un bouton, une carte et un non-lien se résolvent tous en translateY(-0.25rem) + shadow-xl, vous avez expédié le réglage par défaut. Le remède n'est pas plus d'animation, c'est une animation *différenciée*, plus une inertie délibérée. Trois ou quatre gestes de survol distincts, chacun accordé à ce que fait son élément, plus un « pas de hover » assumé sur tout ce qui est décoratif. Voilà la différence entre un mouvement qui communique et un mouvement qui n'est que le modèle en train de garnir l'assiette.
Le hover-lift n'est pas le mal. C'est un affordance parfaitement raisonnable devenu une empreinte digitale à force de répétition pure, exactement comme chaque site généré par IA pioche dans la même poignée de tics. Le correctif est le même que toujours : cessez d'accepter la complétion la plus probable comme une décision de design. Les états de survol ne coûtent presque rien. Les états de survol dotés d'une *intention*, c'est tout le métier.
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.