Busca en todas las páginas de la documentación
Transiciones, keyframes y motion — interacciones de UI fluidas con Tailwind CSS v4.
Tarjeta de receta de referencia rápida — lista para copiar y pegar.
// Transiciones
<button className="transition-colors duration-200 ease-in-out hover:bg-blue-600">
<div className="transition-all duration-300 hover:scale-105 hover:shadow-lg">
<div className="transition-opacity duration-150 opacity-0 data-[visible]:opacity-100">
// Animaciones integradas
<div className="animate-spin"> {/* rotación infinita */}
<div className="animate-ping"> {/* pulso estilo radar */}
<div className="animate-pulse"> {/* pulso suave de opacidad */}
<div className="animate-bounce"> {/* rebote */}
// Animación personalizada con keyframes
<div className="animate-fade-in"> {/* definida en @theme */}
// Movimiento reducido
<div className="motion-safe:animate-bounce motion-reduce:animate-none">/* globals.css — define animaciones personalizadas */
@import "tailwindcss";
@theme {
--animate-fade-in: fade-in 0.3s ease-out;
--animate-slide-up: slide-up 0.4s ease-out;
--animate-scale-in: scale-in 0.2s ease-out;
}
@keyframes fade-in
Cuándo usarlo: Cuando las interacciones de UI se sienten abruptas — agrega transiciones para estados hover, animaciones para elementos que entran y indicadores de carga.
"use client";
import { useState } from "react";
export function AnimatedNotificationList() {
const [notifications, setNotifications] = useState<
{ id: number; text: string; type: "info" |
/* Agrégalos a globals.css */
@theme {
--animate-slide-in-right: slide-in-right 0.3s ease-out;
--animate-shrink: shrink 3s linear forwards;
}
@keyframes slide-in-right {
from { opacity: 0; transform: translateX(100%); }
to
Lo que demuestra esto:
animation-fill-mode: forwardsactive:scale-95 para feedback al presionar el botóntransition-* controlan qué propiedades CSS transicionan, la duración y la curva de aceleraciónanimate-* aplican la abreviatura animation — nombre, duración, timing, iteración--animate-* en @theme, emparejadas con @keyframesmotion-safe: y motion-reduce: respetan la media query prefers-reduced-motionGrupos de propiedades de transición:
// Propiedades específicas (mejor rendimiento)
<div className="transition-transform duration-300"> {/* solo transform */}
<div className="transition-colors duration-200"> {/* solo propiedades de color */}
<div className="transition-opacity duration-150"> {/* solo opacity */}
<div className
Animaciones de entrada escalonadas:
{items.map((item, i) => (
<div
key={item.id}
className="animate-fade-in opacity-0"
style={{ animationDelay: `${i * 100}ms`, animationFillMode: "forwards" }}
>
{item.name}
Acordeón solo con CSS:
<details className="group rounded border">
<summary className="cursor-pointer p-4 font-medium">
Haz clic para expandir
<span className="ml-2 inline-block transition-transform group-open:rotate-90">
▶
</span>
</summary>
<div
Skeleton de carga:
function Skeleton({ className }: { className?: string }) {
return (
<div className={`animate-pulse rounded bg-gray-200 dark:bg-gray-700 ${className}`} />
);
}
// Uso
<div className=
// Retraso de animación como prop
function AnimatedItem({
delay,
children,
}: {
delay: number;
children: React.ReactNode;
}) {
return (
<div
className=
transition-all incluye propiedades de layout — Transicionar width, height o padding provoca recálculo de layout y jank. Solución: Usa transition-transform y transition-opacity para animaciones fluidas a 60fps.
Animación al montar — Las animaciones CSS se ejecutan en el renderizado inicial, lo que puede causar un flash. Solución: Empieza con opacity-0 y usa animation-fill-mode: forwards para terminar en opacidad completa.
prefers-reduced-motion es importante — Los usuarios con trastornos vestibulares pueden verse afectados por el movimiento. Solución: Envuelve las animaciones en motion-safe: o proporciona motion-reduce:animate-none.
animate-* se reproduce en cada cambio de key de re-renderizado — Cambiar la key de React desmonta el elemento y reproduce la animación. Esto puede ser intencional o un bug.
Capas GPU — Usa will-change-transform con moderación. Demasiadas capas GPU desperdician memoria. Solución: Agrega will-change solo a elementos que se están animando activamente.
| Alternativa | Cuándo usarla | Cuándo no usarla |
|---|---|---|
| Framer Motion | Necesitas animaciones basadas en gestos, de layout y de salida | Solo necesitas transiciones hover simples |
CSS @starting-style | Necesitas animaciones de entrada nativas en CSS (sin JS) | Necesitas amplio soporte de navegadores hoy |
| GSAP | Necesitas animaciones complejas basadas en líneas de tiempo | Las transiciones CSS simples son suficientes |
| React Spring | Quieres animación basada en física en React | Prefieres soluciones nativas de CSS |
| View Transitions API | Quieres transiciones animadas de página a página | Necesitas animaciones a nivel de elemento |
transition-* y animate-*?transition-* — se activa en cambios de estado (hover, focus, cambio de clase); controla qué propiedades transicionan, la duración y la curva de aceleraciónanimate-* — aplica una animación @keyframes que se ejecuta al montar o cuando se dispara@theme {
--animate-fade-in: fade-in 0.3s ease-out;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}Luego usa className="animate-fade-in".
transition-transform sobre transition-all?transition-all incluye propiedades de layout como width y height, que provocan recálculo de layout y jank. transition-transform y transition-opacity se ejecutan en la GPU a 60fps.
{items.map((item, i) => (
<div
key={item.id}
className="animate-fade-in opacity-0"
style={{ animationDelay: `${i * 100}ms`, animationFillMode: "forwards" }}
>
Envuelve las animaciones en motion-safe: o proporciona un fallback con motion-reduce:animate-none. Estos se mapean a la media query prefers-reduced-motion.
animate-spin — rotación infinitaanimate-ping — pulso estilo radaranimate-pulse — pulso suave de opacidadanimate-bounce — efecto de rebotefunction Skeleton({ className }: { className?: string }) {
return (
<div className={`animate-pulse rounded bg-gray-200 dark:bg-gray-700 ${className}`} />
);
}Las animaciones CSS se ejecutan desde el estado actual del elemento. Si el elemento empieza con opacity: 1, el primer frame es visible. Solución: empieza con opacity-0 y usa animation-fill-mode: forwards (o [animation-fill-mode:forwards]).
will-change-transform en muchos elementos. ¿Es seguro?No. Cada elemento con will-change crea una capa GPU. Demasiadas capas desperdician memoria. Agrega will-change solo a elementos que se están animando activamente y elimínalo después.
function handleTransitionEnd(e: React.TransitionEvent<HTMLDivElement>) {
if (e.propertyName === "opacity") {
// transición de opacidad completada
}
}function AnimatedItem({
delay,
children,
}: {
delay: number;
children: React.ReactNode;
}) {
return (
<div
className=
@theme para animaciones