Skip to main content

Animations & Transitions Cheatsheet

Transition Shorthand

/* transition: property duration easing delay */
transition: background-color 0.2s ease 0s;

/* Multiple — comma separated */
transition:
background-color 0.2s ease,
transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 0.2s ease;

/* Properties */
transition-property: transform, opacity;
transition-duration: 0.3s, 0.2s;
transition-timing-function: ease, ease-out;
transition-delay: 0s, 0.1s;

/* Best practice: name specific properties (not 'all') */
/* GPU-cheap: transform, opacity */
/* Expensive: width, height, top, left, background */

Animation Shorthand

/* animation: name duration easing delay iterations direction fill-mode state */
animation: fadeIn 0.5s ease 0.2s 1 normal both running;

/* Common patterns */
animation: fadeIn 0.5s ease both; /* Simple entrance */
animation: spin 1s linear infinite; /* Infinite loop */
animation: pulse 2s ease-in-out infinite alternate; /* Ping-pong */
animation: bounce 0.6s cubic-bezier(0.34,1.56,0.64,1) both;

/* Properties */
animation-name: fadeIn;
animation-duration: 0.5s;
animation-timing-function: ease;
animation-delay: 0.2s;
animation-iteration-count: 1 | infinite | 3;
animation-direction: normal | reverse | alternate | alternate-reverse;
animation-fill-mode: none | forwards | backwards | both;
animation-play-state: running | paused;

animation-fill-mode Reference

ValueBefore animationAfter animation
noneNo stylesNo styles (snaps back)
forwardsNo stylesKeeps last keyframe
backwardsApplies first keyframe during delayNo styles
bothFirst keyframe during delayKeeps last keyframe ✅

Use both for entrance animations — almost always correct.


Easing Quick Reference

/* Keywords */
ease → cubic-bezier(0.25, 0.1, 0.25, 1) /* Default — gentle S */
linear → constant speed
ease-in → slow → fast (elements LEAVING)
ease-out → fast → slow (elements ENTERING) ✅ most used
ease-in-out → slow → fast → slow (elements STAYING)

/* Presets for production */
--ease-snappy: cubic-bezier(0.2, 0, 0, 1); /* iOS/Material feel */
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); /* Springy overshoot */
--ease-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* Bigger spring */
--ease-enter: cubic-bezier(0, 0, 0.2, 1); /* Material decelerate */
--ease-exit: cubic-bezier(0.4, 0, 1, 1); /* Material accelerate */

Common @keyframes Library

/* Fade in */
@keyframes fadeIn {
from { opacity: 0; }
}

/* Fade up (most popular entrance) */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(24px); }
}

/* Fade in + scale */
@keyframes popIn {
from { opacity: 0; transform: scale(0.9); }
}

/* Slide in from left */
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-24px); }
}

/* Slide in from right */
@keyframes slideInRight {
from { opacity: 0; transform: translateX(24px); }
}

/* Bounce */
@keyframes bounceIn {
0% { transform: scale(0); }
60% { transform: scale(1.1); }
80% { transform: scale(0.95); }
100% { transform: scale(1); }
}

/* Spin (loading) */
@keyframes spin {
to { transform: rotate(360deg); }
}

/* Pulse (ping) */
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}

/* Skeleton shimmer */
@keyframes shimmer {
from { background-position: -200% center; }
to { background-position: 200% center; }
}
.c-skeleton {
background: linear-gradient(90deg, var(--color-surface) 25%, var(--color-surface-2) 50%, var(--color-surface) 75%);
background-size: 200%;
animation: shimmer 1.5s ease-in-out infinite;
}

/* Typewriter cursor */
@keyframes blink {
50% { border-color: transparent; }
}

Duration Guidelines

InteractionDurationWhy
Button hover color0.15–0.2sSnappy feel
Button hover transform0.2–0.25sSlight drag
Menu/dropdown open0.2sFast — responsive
Modal open0.3sVisual weight
Modal close0.2sQuicker than open
Page section entrance0.5–0.7sCinematic
Loading spinner1sSteady rhythm
Skeleton shimmer1.5sSlow wave
Hero animation0.6–1sDramatic

prefers-reduced-motion (Always Required)

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

/* Or: provide alternative static state */
@media (prefers-reduced-motion: reduce) {
.c-hero { animation: none; opacity: 1; transform: none; }
}

Transform Reference

/* 2D Transforms */
transform: translateX(20px) translateY(-10px);
transform: translate(20px, -10px); /* Shorthand */
transform: scale(1.05); /* Uniform scale */
transform: scale(1.5, 0.8); /* scaleX, scaleY */
transform: rotate(45deg);
transform: skewX(10deg) skewY(5deg);

/* 3D Transforms */
transform: rotateX(45deg); /* Flip top/bottom */
transform: rotateY(180deg); /* Card flip */
transform: rotateZ(45deg); /* = rotate() */
transform: perspective(500px) rotateY(30deg); /* needs perspective */

/* Transform Origin */
transform-origin: center; /* Default */
transform-origin: top left; /* Scale/rotate from corner */
transform-origin: 50% 50%;

/* 3D space on parent */
perspective: 1000px; /* Set on parent then child uses 3D */
transform-style: preserve-3d; /* Children exist in 3D space */
backface-visibility: hidden; /* Hide back of flipped element */