Toolypet
Back to Blog
CSS

CSS Animation & Transition Complete Guide 2026

The differences between CSS transition and animation, how to use them, and performance optimization. A practical guide to creating smooth UI interactions.

Toolypet Team

Toolypet Team

Development Team

8 min read

CSS Animation & Transition Complete Guide 2026

Button hover effects, loading spinners, page transitions... Animations are essential in modern web development.

Learn how to create smooth interactions with CSS alone, without JavaScript.


Transition vs Animation

FeatureTransitionAnimation
TriggerRequires state change (:hover, etc.)Can auto-play
KeyframesStart-end only (2)Unlimited
LoopNot possibleInfinite loop possible
DirectionForward onlyReverse, alternate possible
Use caseSimple state changesComplex animations

CSS Transition

Basic Syntax

transition: property duration timing-function delay;
PropertyDescriptionDefault
propertyProperty to transitionall
durationDuration0s
timing-functionEasing curveease
delayStart delay0s

Basic Examples

/* Single property */
.button {
  background: #3b82f6;
  transition: background 0.3s ease;
}

.button:hover {
  background: #2563eb;
}

/* Multiple properties */
.card {
  transform: translateY(0);
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
  transform: translateY(-5px);
  box-shadow: 0 20px 40px rgba(0,0,0,0.2);
}

Timing Functions

/* Built-in functions */
.ease { transition-timing-function: ease; }         /* Default */
.linear { transition-timing-function: linear; }     /* Constant speed */
.ease-in { transition-timing-function: ease-in; }   /* Slow start */
.ease-out { transition-timing-function: ease-out; } /* Slow end */
.ease-in-out { transition-timing-function: ease-in-out; }

/* Custom bezier curve */
.custom { transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); }

/* Stepped */
.steps { transition-timing-function: steps(4, end); }

Popular Bezier Curves

/* Bounce */
.bounce { transition: all 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55); }

/* Smooth */
.smooth { transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); }

/* Snap */
.snap { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }

CSS Animation

Basic Syntax

@keyframes animationName {
  from { /* Start state */ }
  to { /* End state */ }
}

/* Or */
@keyframes animationName {
  0% { /* Start */ }
  50% { /* Middle */ }
  100% { /* End */ }
}

.element {
  animation: name duration timing-function delay iteration-count direction fill-mode;
}

Animation Properties

PropertyValuesDescription
animation-nameKeyframe nameRequired
animation-durationTimeRequired
animation-timing-functionTimingease
animation-delayDelay time0s
animation-iteration-countRepeat count1, infinite
animation-directionDirectionnormal, reverse, alternate
animation-fill-modeEnd statenone, forwards, backwards, both
animation-play-statePlay staterunning, paused

Practical Animation Examples

1. Fade In

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.fade-in {
  animation: fadeIn 0.5s ease forwards;
}

/* Fade in from top */
@keyframes fadeInDown {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.fade-in-down {
  animation: fadeInDown 0.5s ease forwards;
}

2. Loading Spinner

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #e5e7eb;
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

3. Pulse Effect

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}

.pulse {
  animation: pulse 2s ease-in-out infinite;
}

/* Pulse ring */
@keyframes pulseRing {
  0% {
    transform: scale(0.8);
    opacity: 1;
  }
  100% {
    transform: scale(2);
    opacity: 0;
  }
}

.pulse-ring {
  position: relative;
}

.pulse-ring::before {
  content: '';
  position: absolute;
  inset: 0;
  border: 2px solid #3b82f6;
  border-radius: 50%;
  animation: pulseRing 1.5s ease-out infinite;
}

4. Bounce

@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
    animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
  }
  50% {
    transform: translateY(-25%);
    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
  }
}

.bounce {
  animation: bounce 1s infinite;
}

5. Shake (Error)

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
  20%, 40%, 60%, 80% { transform: translateX(5px); }
}

.shake {
  animation: shake 0.5s ease;
}

/* Trigger with JavaScript */
/* element.classList.add('shake'); */

6. Typing Effect

@keyframes typing {
  from { width: 0; }
  to { width: 100%; }
}

@keyframes blink {
  50% { border-color: transparent; }
}

.typing {
  overflow: hidden;
  white-space: nowrap;
  border-right: 3px solid;
  width: 0;
  animation:
    typing 3s steps(30) forwards,
    blink 0.7s step-end infinite;
}

7. Skeleton Loading

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

Using with Transform

Transform Properties

/* Translate */
transform: translateX(100px);
transform: translateY(50px);
transform: translate(100px, 50px);

/* Rotate */
transform: rotate(45deg);
transform: rotateX(45deg); /* 3D */
transform: rotateY(45deg); /* 3D */

/* Scale */
transform: scale(1.5);
transform: scaleX(2);

/* Skew */
transform: skew(10deg);

/* Combined */
transform: translateX(100px) rotate(45deg) scale(1.2);

3D Effects

/* Set perspective on parent */
.perspective-container {
  perspective: 1000px;
}

/* Card flip */
.card-3d {
  transform-style: preserve-3d;
  transition: transform 0.6s;
}

.card-3d:hover {
  transform: rotateY(180deg);
}

.card-front, .card-back {
  backface-visibility: hidden;
}

.card-back {
  transform: rotateY(180deg);
}

Performance Optimization

GPU-Accelerated Properties

/* ✅ GPU accelerated (good performance) */
transform: translateX(100px);
transform: scale(1.1);
transform: rotate(45deg);
opacity: 0.5;

/* ❌ CPU computed (causes reflow) */
left: 100px;
width: 200px;
height: 200px;
margin: 10px;

will-change

/* Hint for animation targets */
.animated {
  will-change: transform, opacity;
}

/* Only on hover */
.card:hover {
  will-change: transform;
}

/* Recommended to remove after animation */

Avoiding Reflow

/* ❌ Pattern to avoid */
.bad {
  animation: move 1s infinite;
}
@keyframes move {
  to { left: 100px; } /* Causes reflow */
}

/* ✅ Recommended pattern */
.good {
  animation: moveGood 1s infinite;
}
@keyframes moveGood {
  to { transform: translateX(100px); } /* GPU accelerated */
}

Accessibility

Respecting Reduced Motion Preferences

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

/* Or selectively */
@media (prefers-reduced-motion: reduce) {
  .animated {
    animation: none;
  }

  .transition {
    transition: none;
  }
}

Practical UI Patterns

Button Hover

.button {
  background: #3b82f6;
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.button:hover {
  background: #2563eb;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(59,130,246,0.4);
}

.button:active {
  transform: translateY(0);
}

Menu Dropdown

.dropdown {
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  transition: all 0.2s ease;
}

.menu:hover .dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

Modal Opening

.modal-overlay {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease;
}

.modal-overlay.active {
  opacity: 1;
  visibility: visible;
}

.modal {
  transform: scale(0.9) translateY(20px);
  transition: transform 0.3s ease;
}

.modal-overlay.active .modal {
  transform: scale(1) translateY(0);
}

Staggered Appearance

.item {
  opacity: 0;
  transform: translateY(20px);
  animation: fadeInUp 0.5s ease forwards;
}

.item:nth-child(1) { animation-delay: 0.1s; }
.item:nth-child(2) { animation-delay: 0.2s; }
.item:nth-child(3) { animation-delay: 0.3s; }

@keyframes fadeInUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

FAQ

Q1: Transition doesn't work

A: Check:

  • Is the property animatable?
  • Transitions don't work from display: none
  • Are start and end values explicitly defined?

Q2: How to keep state after animation ends?

A: Use animation-fill-mode: forwards

.element {
  animation: fadeIn 1s ease forwards;
}

Q3: Performance is poor

A:

  • Only animate transform and opacity
  • Use will-change appropriately
  • Avoid animating too many elements

Q4: CSS or JavaScript animations?

A:

  • Simple transitions: CSS
  • Complex sequences: JavaScript (GSAP, etc.)
  • User input response: JavaScript
  • Auto loops: CSS

Conclusion

CSS animation key points:

  1. Transition: Responds to state changes
  2. Animation: Auto-play, keyframes
  3. Transform: GPU accelerated, excellent performance
  4. Timing: ease, cubic-bezier
  5. Accessibility: Respect prefers-reduced-motion

Related Tools

ToolPurpose
Animation GeneratorGenerate keyframes
Transform GeneratorTransform effects
Filter GeneratorCSS filters
CSSanimationtransitionanimationsUIinteraction

About the Author

Toolypet Team

Toolypet Team

Development Team

The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.

Web DevelopmentCSS ToolsDeveloper ToolsSEOSecurity