What Are CSS Animations?
CSS animations let you smoothly transition an element between multiple style states — all without a single line of JavaScript. The secret ingredient is the @keyframes at-rule, which defines the exact CSS styles an element should have at each point during the animation sequence. Combined with animation properties on the element itself, you get full control over timing, looping, direction, and more.
Before CSS animations became mainstream, developers relied on JavaScript setInterval loops or jQuery's .animate() to move elements around. CSS animations run on the browser's compositor thread (when animating transform or opacity), making them far smoother and less CPU-intensive than script-based alternatives.
The @keyframes At-Rule
A @keyframes rule describes an animation sequence. You give it a name and then define style snapshots at percentage milestones (or using from / to keywords).
/* Simplest possible keyframe: from → to */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Multi-stop keyframe */
@keyframes bounce {
0% { transform: translateY(0); }
40% { transform: translateY(-30px); }
60% { transform: translateY(-20px); }
80% { transform: translateY(-10px); }
100% { transform: translateY(0); }
}
You can stack multiple properties at a single keyframe stop, and you can even share stops using a comma list:
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.08);
opacity: 0.85;
}
}
Attaching an Animation to an Element
Once you've defined your keyframes, apply the animation using the animation shorthand or individual properties.
.box {
/* Shorthand: name | duration | timing-function | delay | iteration-count | direction | fill-mode */
animation: fadeIn 0.6s ease-out 0s 1 normal forwards;
}
/* Equivalent longhand */
.box {
animation-name: fadeIn;
animation-duration: 0.6s;
animation-timing-function: ease-out;
animation-delay: 0s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
}
Understanding Every Animation Property
animation-name
The animation-name property links the element to a @keyframes rule by its identifier. You can apply multiple animations by comma-separating names:
.element {
animation-name: fadeIn, pulse;
animation-duration: 0.4s, 2s;
}
animation-duration
Sets how long one cycle of the animation takes. Always provide a unit — s (seconds) or ms (milliseconds). Omitting the unit will break the animation silently.
animation-timing-function
Controls the pace of the animation between keyframes. Common values:
ease— slow start, fast middle, slow end (default)linear— constant speed throughoutease-in— starts slowly, acceleratesease-out— starts fast, deceleratesease-in-out— slow at both endscubic-bezier(x1, y1, x2, y2)— custom curvesteps(4, end)— discrete step animation (great for sprite sheets)
/* Custom spring-like bounce */
.spring {
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Sprite sheet animation: 8 frames */
.sprite {
animation-timing-function: steps(8, end);
}
animation-delay
Postpones the start of the animation. A negative delay makes the animation start mid-cycle, which is useful for staggered group animations:
.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }
/* -0.5s would start 0.5 seconds INTO the animation cycle immediately */
.item { animation-delay: -0.5s; }
animation-iteration-count
How many times the animation runs. Use a number or infinite for a loop that never stops:
.spinner { animation-iteration-count: infinite; }
.flash { animation-iteration-count: 3; }
.half { animation-iteration-count: 0.5; } /* plays half a cycle */
animation-direction
Controls which direction the keyframes play:
normal— 0% → 100% every cycle (default)reverse— 100% → 0% every cyclealternate— first cycle forward, second backward, etc.alternate-reverse— first cycle backward, then alternates
.pendulum {
animation-name: swing;
animation-duration: 1s;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
animation-fill-mode
animation-fill-mode is one of the most misunderstood properties. It determines what styles apply to the element before the animation starts and after it ends.
none— element reverts to original styles outside the animation (default)forwards— element retains the final keyframe's styles after animation endsbackwards— element applies the first keyframe's styles during the delay periodboth— applies rules of bothforwardsandbackwards
/* Without forwards, the element will pop back to opacity:0 after fading in */
.fade-in {
opacity: 0;
animation: fadeIn 0.5s ease-out forwards;
}
animation-play-state
Toggle an animation on/off without removing it — perfect for pause buttons:
.spinner { animation: spin 1s linear infinite; }
.spinner.paused { animation-play-state: paused; }
Practical CSS Animation Examples
Loading Spinner
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #e0e0e0;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
Skeleton Screen Shimmer
@keyframes shimmer {
from { background-position: -200% 0; }
to { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 6px;
height: 16px;
}
Entrance Animation with Stagger
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: slideUp 0.5s ease-out both;
}
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 80ms; }
.card:nth-child(3) { animation-delay: 160ms; }
.card:nth-child(4) { animation-delay: 240ms; }
Triggering Animations with JavaScript
You can add and remove CSS classes to trigger animations dynamically. A common pattern is to remove a class, force a reflow, then re-add it:
function replay(element) {
element.classList.remove('animate');
// Force reflow so the browser registers the class removal
void element.offsetWidth;
element.classList.add('animate');
}
document.querySelector('.btn').addEventListener('click', () => {
replay(document.querySelector('.box'));
});
Respecting User Preferences
Always wrap heavy animations in a prefers-reduced-motion media query. Many users experience motion sickness or have vestibular disorders — this is an accessibility requirement, not a nice-to-have:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Debugging CSS Animations
Browser DevTools make animation debugging much easier. In Chrome DevTools, open the Animations panel (More Tools → Animations) to scrub through animations frame-by-frame, slow them down, and inspect timing curves. You can also use our Live HTML/CSS/JS Editor to prototype and iterate on animations in real time without any setup.
Pro Tip: Add animation-play-state: paused temporarily while building complex multi-step keyframes. It lets you inspect the element's style at the first keyframe before the animation runs away.
Multiple Animations on One Element
An element can run several @keyframes simultaneously. List them as comma-separated values in each animation property:
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
@keyframes glow {
0%, 100% { box-shadow: 0 0 8px #3b82f6; }
50% { box-shadow: 0 0 24px #3b82f6, 0 0 48px #60a5fa; }
}
.orb {
animation:
float 3s ease-in-out infinite,
glow 2s ease-in-out infinite;
}
CSS Animation vs CSS Transition
Transitions are for simple A→B state changes triggered by user interaction (hover, focus). Animations with @keyframes are for multi-step sequences that can loop, delay, and run automatically on page load. Use transitions for UI micro-interactions; use animations for looping effects, entrances, and complex sequences.
Performance Considerations
For best performance, stick to animating only transform and opacity. Animating properties like width, height, top, or padding triggers expensive layout recalculations. See our article on CSS animation performance for a deep dive. You can experiment with all these techniques in our Live HTML/CSS/JS Editor.