CSS Animation Generator: Create Smooth Web Animations

📅 April 10, 2026 ⏱ 14 min read 🏷 CSS, Performance

Types of CSS Animation

CSS offers two primary mechanisms for creating animations, each suited to different use cases. Understanding when to use each is the first step to creating smooth, performant web animations.

Feature Transitions Keyframe Animations
Trigger State change (:hover, class toggle) Automatic or triggered
Complexity Simple A → B Multi-step sequences
Repetition One-shot (per state change) Looping or counted
Control Limited Full (direction, delay, iteration)
Best For Hover effects, state changes Load animations, complex sequences

CSS Transitions: The Foundation

Transitions are the simplest form of CSS animation. They smoothly interpolate between property values when an element's state changes. The syntax is straightforward:

/* Shorthand syntax */
.button {
    background: #4361ee;
    color: #fff;
    padding: 12px 24px;
    border-radius: 8px;
    border: none;
    cursor: pointer;

    /* property | duration | timing-function | delay */
    transition: transform 0.3s ease, box-shadow 0.3s ease, background 0.3s ease;
}

.button:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(67, 97, 238, 0.4);
    background: #3450d1;
}

/* Focus state for accessibility */
.button:focus-visible {
    outline: 3px solid #ff9f1c;
    outline-offset: 2px;
}

Transition Timing Functions

The timing function controls the acceleration curve of your animation. The built-in options serve different purposes:

Function Feel Best For
ease Starts fast, slows, fast finish General purpose (default)
ease-in Starts slow, accelerates Elements leaving the screen
ease-out Starts fast, decelerates Elements entering the screen
ease-in-out Slow start and end State toggles, modals
linear Constant speed Progress bars, spinners

CSS Keyframe Animations

For more complex animations, @keyframes gives you full control over every step of the animation sequence:

/* Fade-in and slide up */
@keyframes fadeInUp {
    from {
        opacity: 0;
        transform: translateY(30px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Multi-step bounce animation */
@keyframes bounce {
    0%, 100% {
        transform: translateY(0);
        animation-timing-function: ease-out;
    }
    50% {
        transform: translateY(-20px);
        animation-timing-function: ease-in;
    }
}

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

/* Applying animations */
.hero-text {
    animation: fadeInUp 0.8s ease-out both;
}

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

Animation Properties

The animation shorthand combines several sub-properties:

/* Full shorthand: name | duration | timing | delay | iteration | direction | fill | play */
.card {
    animation: fadeInUp 0.6s ease-out 0.2s 1 normal both running;
}

/* Breakdown of each property */
.card {
    animation-name: fadeInUp;
    animation-duration: 0.6s;      /* How long */
    animation-timing-function: ease-out;  /* Speed curve */
    animation-delay: 0.2s;         /* Wait before starting */
    animation-iteration-count: 1;  /* How many times */
    animation-direction: normal;   /* normal, reverse, alternate */
    animation-fill-mode: both;     /* Styles before/after animation */
    animation-play-state: running; /* running or paused */
}

🎯 Pro Tip: Use animation-fill-mode: both to apply the from styles before the animation starts (useful with delays) and the to styles after it ends. This prevents the "flash" of unstyled content before delayed animations begin.

Timing Functions Deep Dive

The cubic-bezier() function gives you precise control over animation timing by defining a custom acceleration curve. It takes four values representing two control points:

/* Common cubic-bezier presets */
/* Material Design standard easing */
.material-standard { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); }

/* Material Design decelerate */
.material-decelerate { transition-timing-function: cubic-bezier(0, 0, 0.2, 1); }

/* Material Design accelerate */
.material-accelerate { transition-timing-function: cubic-bezier(0.4, 0, 1, 1); }

/* Snappy, elastic feel */
.snappy { transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); }

/* Dramatic ease-out */
.dramatic { transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }

The dramatic cubic-bezier (cubic-bezier(0.16, 1, 0.3, 1)) has become one of the most popular custom timing functions in modern web design. It creates a quick start with a long, graceful deceleration that feels premium and polished.

Performance: The 60fps Rule

Smooth animation requires hitting 60 frames per second — that means the browser has approximately 16.67ms per frame to calculate and render every change. Understanding which CSS properties trigger expensive operations is critical.

The CSS Performance Spectrum

Category Properties Performance Why
Composite transform, opacity ✅ Excellent GPU-only, no layout or paint
Paint color, background, box-shadow, border-radius ⚠️ Moderate Triggers repaint, may skip frames
Layout width, height, margin, padding, top, left ❌ Expensive Triggers full layout recalculation

⚠️ Golden Rule: Only animate transform and opacity for 60fps animations. These are the only properties that can be handled entirely by the GPU compositor without triggering layout or paint calculations.

Common Performance Mistakes

/* ❌ BAD: Animating layout properties */
.card:hover {
    left: 20px;
    width: 300px;
    margin-top: 10px;
}

/* ✅ GOOD: Using transform instead */
.card:hover {
    transform: translateX(20px) scaleX(1.2);
}

/* ❌ BAD: Animating box-shadow (triggers paint every frame) */
.card:hover {
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
    transition: box-shadow 0.3s;
}

/* ✅ GOOD: Animate opacity on a separate shadow element */
.card-shadow {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
    opacity: 0;
    transition: opacity 0.3s;
}
.card:hover .card-shadow {
    opacity: 1;
}

/* ✅ BETTER: Use filter: drop-shadow (composited in some browsers) */
.card:hover {
    filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.3));
    transition: filter 0.3s;
}

GPU Acceleration

Forcing an element onto its own GPU layer can dramatically improve animation performance:

/* Promote to GPU layer */
.animated-element {
    will-change: transform;
    /* Or use: transform: translateZ(0); */
    /* Or use: backface-visibility: hidden; */
}

💡 Important: Don't overuse will-change. Each promoted layer consumes GPU memory. Only use it on elements that are actively animating, and remove it after the animation completes.

Common Animation Patterns

Staggered List Animation

/* Stagger entrance of list items */
@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateX(-20px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

.list-item {
    animation: slideIn 0.4s ease-out both;
}

/* Use CSS custom property for stagger delay */
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 60ms; }
.list-item:nth-child(3) { animation-delay: 120ms; }
.list-item:nth-child(4) { animation-delay: 180ms; }
.list-item:nth-child(5) { animation-delay: 240ms; }

/* Or with a single rule using calc */
.list-item {
    animation-delay: calc(var(--index) * 60ms);
}

Page Transition Effects

/* Smooth page content transitions */
@keyframes pageEnter {
    from {
        opacity: 0;
        transform: translateY(10px);
    }
}

.page-content {
    animation: pageEnter 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Skeleton loading animation */
@keyframes shimmer {
    0% { background-position: -200% 0; }
    100% { background-position: 200% 0; }
}

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

Micro-interactions

/* Button press effect */
.button {
    transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.button:active {
    transform: scale(0.97);
}

/* Checkbox animation */
@keyframes checkmark {
    0% {
        stroke-dashoffset: 24;
    }
    100% {
        stroke-dashoffset: 0;
    }
}

.checkbox-icon {
    stroke-dasharray: 24;
    stroke-dashoffset: 24;
    transition: stroke-dashoffset 0.3s ease;
}
.checkbox-checked .checkbox-icon {
    stroke-dashoffset: 0;
}

/* Toggle switch */
.toggle-track {
    transition: background 0.3s ease;
}
.toggle-thumb {
    transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.toggle-active .toggle-thumb {
    transform: translateX(24px);
}

Scroll-Driven Animations

In 2026, scroll-driven animations have become a powerful native CSS feature. Instead of JavaScript scroll listeners, you can tie animations directly to scroll position:

/* Scroll-driven fade-in (2026 CSS) */
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
}

.scroll-reveal {
    animation: fadeIn linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 50%;
}

/* Parallax scrolling */
@keyframes parallax {
    from { transform: translateY(0); }
    to { transform: translateY(-100px); }
}

.parallax-bg {
    animation: parallax linear both;
    animation-timeline: scroll();
    animation-range: 0px 500px;
}

Scroll-driven animations eliminate the need for Intersection Observer and scroll event listeners for many common patterns, resulting in smoother performance and less JavaScript.

Respecting prefers-reduced-motion

Accessibility requires that animations respect user preferences. Many users experience motion sickness, vestibular disorders, or simply find animations distracting. The prefers-reduced-motion media query lets you provide alternative experiences:

/* Default: full animations */
.card {
    animation: fadeInUp 0.6s ease-out both;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

/* Reduced motion: instant or minimal animation */
@media (prefers-reduced-motion: reduce) {
    .card {
        animation: none;
        opacity: 1;
        transform: none;

        /* Keep transitions but make them instant */
        transition-duration: 0.01ms !important;
    }

    /* Or: keep transitions but remove motion */
    .card:hover {
        transform: none;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }

    /* Remove auto-playing animations */
    .auto-animate {
        animation: none !important;
    }
}

🎯 Best Practice: Don't just disable all animations for reduced-motion users. Remove decorative motion but keep essential state-change feedback. A button hover should still indicate interactivity — just without the movement.

Best Practices & Anti-Patterns

Duration Guidelines

Animation Type Recommended Duration
Micro-interactions (hover, press)100–200ms
State transitions (toggle, expand)200–400ms
Entrance animations (fade in, slide)300–600ms
Page transitions300–500ms
Loading indicators800–1500ms (loop)

Do's ✅

Don'ts ❌

CSS animations have evolved from simple novelties into essential design tools. When used thoughtfully, they guide user attention, provide feedback, create spatial relationships, and make interfaces feel alive and responsive. The key is restraint — the best animations are the ones users notice but don't consciously think about. They feel natural, not flashy. With the performance principles and patterns from this guide, you're equipped to create animations that are both beautiful and blazingly fast.

Create Animations Visually

Design, preview, and export production-ready CSS animation code with our interactive generator. Choose from presets or build custom keyframe animations.

→ Try CSS Animation Generator