CSS Animation Fundamentals
Before diving into the 50 examples, let us establish the two mechanisms CSS gives you for motion: transitions and animations.
Transitions animate a property change between two states. They require a trigger (hover, focus, class toggle) and run once.
.button {
background: #00d4ff;
transition: background 0.3s ease, transform 0.2s ease;
}
.button:hover {
background: #818cf8;
transform: translateY(-2px);
}
Animations use @keyframes to define multi-step sequences. They can loop, alternate, run on load, and do not need a trigger.
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.element {
animation: fadeIn 0.5s ease-out forwards;
}
Only animate transform and opacity. These two properties are composited by the GPU and do not trigger layout or paint recalculations. Animating width, height, margin, top, or left causes the browser to recalculate layout on every frame, which leads to dropped frames and janky motion. Every example in this article follows this rule.
To experiment with timing functions, durations, and keyframe values in real time, use the QTool CSS Animation Playground. It lets you tweak parameters visually and copy the generated CSS.
Button Animations (1-10)
1. Pulse Glow
A soft, repeating glow that draws attention to a call-to-action button.
.pulse-btn {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(0, 212, 255, 0.6); }
70% { box-shadow: 0 0 0 15px rgba(0, 212, 255, 0); }
100% { box-shadow: 0 0 0 0 rgba(0, 212, 255, 0); }
}
2. Gradient Shift
The background gradient continuously shifts position, creating a liquid color effect.
.gradient-btn {
background: linear-gradient(135deg, #00d4ff, #a855f7, #ec4899);
background-size: 200% 200%;
animation: gradientShift 3s ease infinite;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
Generate custom gradient values with the QTool CSS Gradient Generator, then animate them using this pattern.
3. Shake on Hover
A quick shake effect for error states or playful interactions. Hover over the button to see it.
.shake-btn:hover {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
20%, 40%, 60%, 80% { transform: translateX(4px); }
}
4. Scale on Press
.press-btn {
transition: transform 0.1s ease;
}
.press-btn:active {
transform: scale(0.95);
}
5. Slide Underline
.underline-btn {
position: relative;
overflow: hidden;
}
.underline-btn::after {
content: '';
position: absolute;
bottom: 0; left: 0;
width: 100%; height: 2px;
background: currentColor;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.underline-btn:hover::after {
transform: translateX(0);
}
6. Border Draw
.border-draw {
position: relative;
border: 2px solid transparent;
transition: border-color 0.3s;
}
.border-draw::before,
.border-draw::after {
content: ''; position: absolute;
width: 0; height: 0;
border: 2px solid transparent;
transition: all 0.3s;
}
.border-draw::before { top: -2px; left: -2px; }
.border-draw::after { bottom: -2px; right: -2px; }
.border-draw:hover::before {
width: 100%; height: 100%;
border-top-color: #00d4ff;
border-right-color: #00d4ff;
}
.border-draw:hover::after {
width: 100%; height: 100%;
border-bottom-color: #00d4ff;
border-left-color: #00d4ff;
}
7. Icon Slide
.icon-btn .arrow {
display: inline-block;
transition: transform 0.3s ease;
}
.icon-btn:hover .arrow {
transform: translateX(5px);
}
8. Fill from Left
.fill-btn {
position: relative; overflow: hidden;
z-index: 1;
}
.fill-btn::before {
content: '';
position: absolute; inset: 0;
background: #a855f7;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
z-index: -1;
}
.fill-btn:hover::before {
transform: scaleX(1);
}
9. Ripple Effect
.ripple-btn {
position: relative; overflow: hidden;
}
.ripple-btn::after {
content: '';
position: absolute;
width: 100px; height: 100px;
background: rgba(255,255,255,0.3);
border-radius: 50%;
transform: scale(0);
opacity: 1;
pointer-events: none;
}
.ripple-btn:active::after {
animation: ripple 0.6s ease-out;
}
@keyframes ripple {
to { transform: scale(4); opacity: 0; }
}
10. Neon Glow
.neon-btn {
color: #00d4ff;
border: 2px solid #00d4ff;
text-shadow: 0 0 5px #00d4ff;
transition: all 0.3s;
}
.neon-btn:hover {
background: #00d4ff;
color: #fff;
box-shadow: 0 0 20px #00d4ff, 0 0 40px #00d4ff80;
text-shadow: none;
}
Customize the shadow values with the QTool Box Shadow Generator to create your own glow intensity and color.
Loading Animations (11-20)
11. Classic Spinner
.spinner {
width: 40px; height: 40px;
border: 4px solid #222233;
border-top-color: #00d4ff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
12. Bouncing Dots
.bounce-dots span {
width: 12px; height: 12px;
border-radius: 50%;
background: #a855f7;
animation: bounceDot 1.4s infinite ease-in-out both;
}
.bounce-dots span:nth-child(1) { animation-delay: -0.32s; }
.bounce-dots span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounceDot {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
13. Skeleton Shimmer
.skeleton {
background: linear-gradient(
90deg,
#1a1a24 25%, #222233 50%, #1a1a24 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
to { background-position: -200% 0; }
}
14. Progress Bar
.progress-bar {
height: 8px;
background: linear-gradient(90deg, #00d4ff, #a855f7);
border-radius: 4px;
animation: fillBar 2s ease-out forwards;
}
@keyframes fillBar {
from { width: 0; }
to { width: 85%; }
}
15. Rotating Square
.rotating-square {
width: 30px; height: 30px;
background: #00d4ff;
animation: rotateSquare 1.2s infinite ease-in-out;
}
@keyframes rotateSquare {
0% { transform: rotate(0deg); }
25% { transform: rotate(90deg) scale(0.6); }
50% { transform: rotate(180deg) scale(1); }
75% { transform: rotate(270deg) scale(0.6); }
100% { transform: rotate(360deg); }
}
16. Dual Ring
.dual-ring {
width: 50px; height: 50px;
border: 4px solid transparent;
border-top-color: #00d4ff;
border-bottom-color: #a855f7;
border-radius: 50%;
animation: spin 1s linear infinite;
}
17. Bar Wave
.bar-wave { display: flex; gap: 3px; align-items: end; height: 30px; }
.bar-wave span {
width: 4px; background: #00d4ff; border-radius: 2px;
animation: barWave 1.2s ease-in-out infinite;
}
.bar-wave span:nth-child(2) { animation-delay: 0.1s; }
.bar-wave span:nth-child(3) { animation-delay: 0.2s; }
.bar-wave span:nth-child(4) { animation-delay: 0.3s; }
@keyframes barWave {
0%, 40%, 100% { height: 10px; }
20% { height: 30px; }
}
18. Orbit
.orbit-container { position: relative; width: 40px; height: 40px; }
.orbit-dot {
position: absolute;
width: 8px; height: 8px;
background: #ec4899; border-radius: 50%;
top: 0; left: 50%; margin-left: -4px;
animation: orbit 1.5s linear infinite;
transform-origin: 4px 20px;
}
@keyframes orbit { to { transform: rotate(360deg); } }
19. Fade Pulse
.fade-pulse {
animation: fadePulse 1.5s ease-in-out infinite;
}
@keyframes fadePulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
20. Morphing Shape
.morph {
width: 40px; height: 40px;
background: #00d4ff;
animation: morph 2s ease-in-out infinite;
}
@keyframes morph {
0%, 100% { border-radius: 50%; transform: rotate(0deg); }
50% { border-radius: 10%; transform: rotate(180deg); }
}
Hover Effects (21-30)
21. Card Lift
.card {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-6px);
box-shadow: 0 12px 40px rgba(0,0,0,0.3);
}
22. Image Zoom
.image-container { overflow: hidden; border-radius: 12px; }
.image-container img {
transition: transform 0.5s ease;
}
.image-container:hover img {
transform: scale(1.08);
}
23. Flip Card
.flip-container { perspective: 600px; }
.flip-card {
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flip-container:hover .flip-card {
transform: rotateY(180deg);
}
.flip-front, .flip-back {
position: absolute; inset: 0;
backface-visibility: hidden;
}
.flip-back { transform: rotateY(180deg); }
24. Glassmorphism Reveal
.glass-card {
background: rgba(255,255,255,0.03);
backdrop-filter: blur(0px);
border: 1px solid transparent;
transition: all 0.4s ease;
}
.glass-card:hover {
background: rgba(255,255,255,0.08);
backdrop-filter: blur(12px);
border-color: rgba(255,255,255,0.1);
}
Create glassmorphism designs visually with the QTool Glassmorphism Generator and copy the CSS directly.
25. Text Glow
.glow-text {
transition: text-shadow 0.3s ease;
}
.glow-text:hover {
text-shadow: 0 0 10px #00d4ff, 0 0 20px #00d4ff80;
}
26. Background Slide
.bg-slide {
background: linear-gradient(90deg, #00d4ff 50%, transparent 50%);
background-size: 200% 100%;
background-position: right;
transition: background-position 0.4s ease;
}
.bg-slide:hover {
background-position: left;
}
27. Rotate Icon
.rotate-icon {
transition: transform 0.3s ease;
}
.rotate-icon:hover {
transform: rotate(90deg);
}
28. Tilt Perspective
.tilt {
transition: transform 0.3s ease;
}
.tilt:hover {
transform: perspective(500px) rotateX(5deg) rotateY(-5deg);
}
Fine-tune transform values with the QTool CSS Transform Generator.
29. Border Gradient Reveal
.border-reveal {
border: 2px solid transparent;
background-clip: padding-box;
transition: border-color 0.3s;
}
.border-reveal:hover {
border-image: linear-gradient(135deg, #00d4ff, #ec4899) 1;
}
30. Blur to Sharp
.blur-sharp {
filter: blur(2px);
opacity: 0.8;
transition: filter 0.4s ease, opacity 0.4s ease;
}
.blur-sharp:hover {
filter: blur(0);
opacity: 1;
}
Experiment with CSS filter combinations using the QTool CSS Filter Generator.
Page Transitions and Entrances (31-40)
31. Fade In
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in { animation: fadeIn 0.5s ease-out forwards; }
32. Slide Up Fade
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.slide-up { animation: slideUp 0.6s ease-out forwards; }
33. Slide In from Left
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-30px); }
to { opacity: 1; transform: translateX(0); }
}
.slide-left { animation: slideInLeft 0.5s ease-out forwards; }
34. Scale In
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
.scale-in { animation: scaleIn 0.4s ease-out forwards; }
35. Stagger Children
.stagger-list > * {
opacity: 0;
animation: slideUp 0.5s ease-out forwards;
}
.stagger-list > *:nth-child(1) { animation-delay: 0.05s; }
.stagger-list > *:nth-child(2) { animation-delay: 0.1s; }
.stagger-list > *:nth-child(3) { animation-delay: 0.15s; }
.stagger-list > *:nth-child(4) { animation-delay: 0.2s; }
.stagger-list > *:nth-child(5) { animation-delay: 0.25s; }
36. Typewriter
.typewriter {
font-family: monospace;
border-right: 2px solid #22c55e;
white-space: nowrap;
overflow: hidden;
width: 0;
animation:
typing 3s steps(20) 0.5s forwards,
blink 0.7s step-end infinite;
}
@keyframes typing { to { width: 20ch; } }
@keyframes blink { 50% { border-color: transparent; } }
37. Rotate In
@keyframes rotateIn {
from { opacity: 0; transform: rotate(-10deg) scale(0.95); }
to { opacity: 1; transform: rotate(0) scale(1); }
}
.rotate-in { animation: rotateIn 0.5s ease-out forwards; }
38. Blur In
@keyframes blurIn {
from { opacity: 0; filter: blur(10px); }
to { opacity: 1; filter: blur(0); }
}
.blur-in { animation: blurIn 0.6s ease-out forwards; }
39. Clip Path Reveal
@keyframes clipReveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
.clip-reveal { animation: clipReveal 0.7s ease-out forwards; }
40. Drop In (Spring)
@keyframes dropIn {
0% { opacity: 0; transform: translateY(-40px); }
60% { transform: translateY(5px); }
80% { transform: translateY(-3px); }
100% { opacity: 1; transform: translateY(0); }
}
.drop-in { animation: dropIn 0.6s ease-out forwards; }
Scroll and Continuous Animations (41-50)
41. Floating Element
.float {
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15px); }
}
42. Scroll Fade In (IntersectionObserver)
/* CSS */
.scroll-reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.scroll-reveal.visible {
opacity: 1;
transform: translateY(0);
}
/* JavaScript */
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.scroll-reveal')
.forEach(el => observer.observe(el));
43. Parallax on Scroll
.parallax-element {
transition: transform 0.1s linear;
}
/* JavaScript */
window.addEventListener('scroll', () => {
const y = window.scrollY;
document.querySelector('.parallax-element')
.style.transform = `translateY(${y * 0.3}px)`;
});
44. Counter Up
/* Uses CSS @property for animatable custom properties */
@property --num {
syntax: '<integer>';
initial-value: 0;
inherits: false;
}
.counter {
--num: 0;
animation: countUp 2s ease-out forwards;
counter-reset: num var(--num);
}
.counter::after {
content: counter(num);
}
@keyframes countUp {
to { --num: 100; }
}
45. Marquee (Infinite Scroll)
.marquee { overflow: hidden; white-space: nowrap; }
.marquee-content {
display: inline-block;
animation: marquee 15s linear infinite;
}
@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
46. Breathing Glow
.breathing {
animation: breathe 4s ease-in-out infinite;
}
@keyframes breathe {
0%, 100% { box-shadow: 0 0 10px #00d4ff40; }
50% { box-shadow: 0 0 30px #00d4ff80; }
}
47. Scroll-Linked Progress Bar
.scroll-progress {
position: fixed; top: 0; left: 0;
height: 3px; background: #00d4ff;
z-index: 9999;
animation: scrollProgress linear;
animation-timeline: scroll();
}
@keyframes scrollProgress {
to { width: 100%; }
}
48. Text Gradient Shift
.gradient-text {
background: linear-gradient(
90deg, #00d4ff, #a855f7, #ec4899, #00d4ff
);
background-size: 300% 100%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: textGradient 4s linear infinite;
}
@keyframes textGradient {
to { background-position: -300% 0; }
}
49. Wobble
.wobble:hover {
animation: wobble 0.8s ease;
}
@keyframes wobble {
15% { transform: translateX(-10px) rotate(-5deg); }
30% { transform: translateX(8px) rotate(3deg); }
45% { transform: translateX(-6px) rotate(-3deg); }
60% { transform: translateX(4px) rotate(2deg); }
75% { transform: translateX(-2px) rotate(-1deg); }
100% { transform: translateX(0) rotate(0); }
}
50. Reveal on Scroll (CSS-Only with scroll-timeline)
/* Modern browsers (Chrome 115+, Edge 115+) */
.scroll-animate {
opacity: 0;
transform: translateY(30px);
animation: scrollReveal linear both;
animation-timeline: view();
animation-range: entry 0% entry 50%;
}
@keyframes scrollReveal {
to { opacity: 1; transform: translateY(0); }
}
The animation-timeline: view() property ties animation progress to an element's visibility in the viewport. No JavaScript needed. This is a newer feature -- check browser support before using in production.
Performance Best Practices
1. Stick to Transform and Opacity
This is the golden rule repeated deliberately. Animating width, height, margin, padding, top, left, border-width, or font-size triggers layout recalculation on every frame. Use transform: scale() instead of width/height. Use transform: translate() instead of top/left.
2. Use will-change Sparingly
/* Good: apply before animation starts */
.element-about-to-animate {
will-change: transform, opacity;
}
/* Bad: applying to everything */
* { will-change: transform; } /* DO NOT DO THIS */
will-change promotes an element to its own compositor layer, which uses GPU memory. Apply it only to elements that will actually animate, and remove it after the animation completes if possible.
3. Minimize Reflows
Reading layout properties (like offsetHeight) during an animation frame forces the browser to recalculate layout synchronously. This is called a forced reflow. Batch your DOM reads before writes, or use requestAnimationFrame to separate them.
4. Test on Real Devices
Animations that run smoothly on a desktop with a dedicated GPU may stutter on a mid-range phone. Always test your animations on mobile devices. Use Chrome DevTools Performance panel to identify dropped frames.
For minifying your production CSS to strip comments and whitespace from animation code, use the QTool CSS Minifier.
Accessibility: Respecting User Preferences
Some users experience motion sickness, vestibular disorders, or simply prefer reduced visual motion. Every operating system has a "Reduce Motion" setting, and CSS gives you a media query to detect it.
@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;
}
}
This media query effectively disables all animations and transitions for users who have enabled reduced motion. The 0.01ms duration (rather than 0s) ensures that animationend events still fire, which prevents JavaScript that listens for those events from breaking.
Add the prefers-reduced-motion media query to every project that uses animations. It takes 10 seconds to add and makes your site usable for people with vestibular disorders. There is no excuse for skipping it.
Build Animations Visually
Experiment with timing functions, keyframes, and durations in real time. Copy the generated CSS into your project.
Open CSS Animation PlaygroundFrequently Asked Questions
What is the difference between CSS transitions and CSS animations?
CSS transitions animate a property change between two states. They require a trigger (hover, focus, class toggle) and run once. CSS animations use @keyframes to define multi-step sequences that can loop infinitely, alternate direction, and run without a trigger. Use transitions for simple state changes. Use animations for loaders, attention effects, and any motion that needs multiple steps or looping.
How do I make CSS animations smooth and performant?
Animate only transform and opacity. These are handled by the GPU compositor and do not trigger layout or paint recalculations. Avoid animating width, height, margin, padding, top, or left. Use will-change: transform on elements about to be animated. Always test on mobile devices where resources are limited.
How do I respect users who prefer reduced motion?
Use the prefers-reduced-motion media query: @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }. This disables animations for users who have enabled the Reduce Motion setting in their OS.
Can I create CSS animations without writing code?
Yes. Visual generators like the QTool CSS Animation Playground let you build animations by adjusting parameters visually. For specific properties, use the Box Shadow Generator for shadow animations, the Gradient Generator for color effects, and the Transform Generator for movement.
What CSS properties can be animated?
Most properties that accept numeric, color, or length values can be animated: transform, opacity, color, background-color, box-shadow, width, height, margin, padding, font-size, border-radius, and filter. Properties like display, position, and float cannot be animated. For performance, stick to transform and opacity.
Explore 269 Free Developer Tools
CSS animations are just the start. QTool has free tools for gradients, shadows, transforms, filters, colors, and much more. All browser-based, all private.
Browse All Free Tools