CSS Animations: A Beginner's Guide to Keyframes and Transitions (2026)
CSS animations bring web interfaces to life. From subtle hover effects to full-screen page transitions, animations guide user attention, communicate state changes, and make applications feel polished and responsive. The best part is that modern CSS handles most animation needs without a single line of JavaScript. In this comprehensive guide, you'll learn everything you need to know about CSS keyframes and transitions — how they differ, how to write them from scratch, and how to optimize them for performance and accessibility. Whether you're adding a simple fade-in to a landing page or building a complex loading spinner, this is the foundation you need.
CSS Transitions vs CSS Animations: What's the Difference?
Before diving into keyframes, it's important to understand the two distinct mechanisms CSS provides for motion: transitions and animations. They solve different problems and are best suited for different scenarios.
CSS Transitions
A CSS transition smoothly interpolates a property from one value to another when that property changes — typically on user interaction like a hover, focus, or class toggle. You define which property to animate, how long the transition takes, and what easing function to use. Transitions are always two-state: they go from A to B, and optionally back from B to A.
.button {
background-color: #3b82f6;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.button:hover {
background-color: #2563eb;
transform: scale(1.05);
}Transitions are ideal for interactive state changes: hover effects, focus styles, expanding panels, and toggling visibility. They require a triggering event — the property value must actually change for the transition to fire.
CSS Animations
CSS animations, powered by @keyframes, are far more flexible. They can run automatically on page load, loop infinitely, define multiple intermediate steps, alternate direction, and control exactly what happens at every percentage of the animation timeline. Unlike transitions, animations do not need a trigger — they can start as soon as the element renders.
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 0.6s ease-out forwards;
}Use transitions for simple, interactive state changes. Use animations for anything more complex — multi-step sequences, looping effects, entrance animations, or anything that should run without user interaction. If you want to experiment with both approaches visually, the CSS Animation Generator lets you build, preview, and export production-ready animation code instantly.
How @keyframes Work: Syntax Explained
The @keyframes rule defines the stages of an animation. You give the animation a name and then specify what CSS properties should apply at various points in the timeline. The browser smoothly interpolates between those points.
@keyframes slideBounce {
0% {
transform: translateX(-100%);
opacity: 0;
}
60% {
transform: translateX(10%);
opacity: 1;
}
80% {
transform: translateX(-5%);
}
100% {
transform: translateX(0);
}
}You can use from and to as shorthand for 0% and 100%, or define as many percentage stops as you need. Each stop can set different values for any animatable CSS property — transform, opacity, color, background, box-shadow, and many more.
The keyframe name is a custom identifier you choose. It connects the @keyframes definition to the element's animation-name property. Keep names descriptive — fadeInUp, spinLoader, or pulseGlow are all good choices.
The Animation Shorthand Property
Once you've defined your keyframes, you apply them to an element using the animation shorthand property. This single line controls every aspect of how the animation plays.
/* animation: name duration timing-function delay iteration-count direction fill-mode play-state */
.element {
animation: fadeIn 0.6s ease-out 0.2s 1 normal forwards running;
}Here is what each value controls:
- animation-name: The name of the
@keyframesrule to use. Must match exactly. - animation-duration: How long the animation takes to complete one cycle. Accepts seconds (
0.6s) or milliseconds (600ms). - animation-timing-function: The acceleration curve —
ease,linear,ease-in,ease-out,ease-in-out, or a customcubic-bezier(). - animation-delay: Time to wait before the animation starts. Useful for staggering multiple elements.
- animation-iteration-count: How many times the animation repeats. Use a number or
infinitefor continuous looping. - animation-direction: Controls playback direction —
normal,reverse,alternate(forward then backward), oralternate-reverse. - animation-fill-mode: Determines the element's style before and after the animation.
forwardskeeps the final keyframe state,backwardsapplies the first keyframe during the delay, andbothdoes both. - animation-play-state: Allows you to pause and resume —
runningorpaused. Useful for toggling animations on user interaction.
You can also apply multiple animations to a single element by separating them with commas. For example: animation: fadeIn 0.5s ease, slideUp 0.8s ease-out. Building these by hand can be tedious, which is why the CSS Animation Generator is so useful — it gives you a visual interface to configure every parameter and preview the result in real time.
Common Animation Examples with Code
Let's walk through five of the most commonly used CSS animations. Each one is production-ready and can be dropped directly into your projects.
1. Fade In
The fade-in is the most fundamental entrance animation. It transitions an element from invisible to visible, optionally combined with a slight vertical movement for a more dynamic feel.
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}2. Slide In from Left
Slide animations are perfect for off-screen elements entering the viewport — navigation drawers, notification panels, or content sections revealed on scroll.
@keyframes slideInLeft {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.slide-in-left {
animation: slideInLeft 0.6s ease-out forwards;
}3. Bounce
A bounce effect draws attention to an element. This is commonly used for call-to-action buttons, notification badges, or icons that need to stand out.
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-20px);
}
60% {
transform: translateY(-10px);
}
}
.bounce {
animation: bounce 1.2s ease infinite;
}4. Pulse
The pulse animation gently scales an element up and back down, creating a breathing effect. It works well for live indicators, active status dots, and subtle attention cues.
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.08);
opacity: 0.85;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.pulse {
animation: pulse 2s ease-in-out infinite;
}5. Spin
A continuous rotation, most commonly used for loading spinners and refresh icons. The linear timing function ensures a smooth, constant speed rotation.
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 1s linear infinite;
}Each of these animations uses only transform and opacity — the two properties that are GPU-accelerated in all modern browsers. This is a deliberate choice for performance, which we'll cover in detail below. You can generate all of these and more using the CSS Animation Generator, which provides live previews and ready-to-copy code.
Timing Functions Explained
The timing function (also called the easing function) controls the acceleration curve of your animation. It determines whether the motion starts slow and speeds up, starts fast and decelerates, or moves at a constant speed. Choosing the right timing function is the difference between an animation that feels natural and one that feels robotic.
- ease: The default. Starts slow, speeds up in the middle, then slows down at the end. Good for general-purpose animations.
- linear: Constant speed from start to finish. Best for continuous rotations (spinners) or progress bars where uniform motion is expected.
- ease-in: Starts slow and accelerates. Useful for elements leaving the screen — the motion builds momentum as they exit.
- ease-out: Starts fast and decelerates. The best choice for entrance animations — elements arrive quickly and settle gently into position.
- ease-in-out: Slow at both ends, fast in the middle. Works well for animations that both start and end in a resting state, like toggling between two positions.
- cubic-bezier(x1, y1, x2, y2): Full custom control. Define your own curve with four control points. For example,
cubic-bezier(0.68, -0.55, 0.27, 1.55)creates an overshoot-and-settle effect that feels springy and organic.
/* Custom spring-like easing */
.spring-animation {
animation: slideInLeft 0.7s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards;
}
/* Snappy deceleration for UI elements */
.snappy {
animation: fadeIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}As a general rule, use ease-out for entrances, ease-in for exits, and ease-in-out for state changes. Reserve linear for continuous looping animations. For everything else, a custom cubic-bezier curve will give you the most polished result.
Performance Tips for CSS Animations
Not all CSS properties are equal when it comes to animation performance. Some properties can be animated cheaply, while others force the browser to recalculate layout, repaint pixels, or both — leading to janky, stuttering motion. Understanding this distinction is critical for building smooth 60fps animations.
The GPU-Accelerated Properties
Two CSS properties are composited on the GPU in all modern browsers: transform and opacity. When you animate these, the browser can offload the work to the graphics card, avoiding expensive main-thread calculations entirely. This is why every example in this guide uses these two properties exclusively.
Properties to Avoid Animating
Layout-triggering properties like width, height, top, left, margin, and padding force the browser to recalculate the position of every element on the page. Animating box-shadow or border-radius triggers repaints. Both are significantly more expensive than compositing.
Instead of animating width, use transform: scaleX(). Instead of top or left, use transform: translate(). Instead of fading an element by changing visibility or display, animate opacity.
The will-change Property
.animated-element {
will-change: transform, opacity;
}
/* Remove will-change when the animation is done */
.animated-element.done {
will-change: auto;
}The will-change property hints to the browser that an element will be animated, allowing it to set up GPU layers in advance. Use it sparingly — applying will-change to too many elements wastes memory. Add it just before an animation starts and remove it when the animation finishes. For static elements with permanent animations (like spinners), keeping it is fine. If you need to optimize your stylesheets alongside your animations, the CSS Minifier and Beautifier helps keep your production CSS lean.
Accessibility: Respecting prefers-reduced-motion
Animations can cause discomfort for users with vestibular disorders, motion sensitivity, or certain neurological conditions. Both macOS and Windows provide a system-level setting to reduce motion, and CSS gives you a media query to detect it. Respecting this preference is not optional — it is a core accessibility requirement.
/* Default: full animations */
.card {
animation: fadeInUp 0.6s ease-out forwards;
}
/* Reduced motion: instant transitions only */
@media (prefers-reduced-motion: reduce) {
.card {
animation: none;
opacity: 1;
transform: none;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}The nuclear approach — setting all animation and transition durations to near-zero — is a solid baseline. For a more refined experience, you can selectively keep subtle, non-motion animations (like color changes or opacity fades) while removing transforms, slides, and bounces. The key principle is that no animation should be essential for understanding or using your interface. Every animated state change should also work without the animation.
Practical Use Cases
Now that you understand the mechanics, let's look at where CSS animations have the most impact in real-world applications.
Loading Spinners
A custom CSS spinner replaces heavy GIF or SVG loading indicators. Using a single div with border styling and the spin animation, you get a crisp, scalable, and lightweight loader. Combine it with a gradient border for a more polished look — the CSS Gradient Generator can help you find the right color combination.
.loader {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}Hover Effects
CSS transitions on hover are one of the most common uses of motion on the web. Buttons that shift color, cards that lift with a shadow, and images that scale slightly on hover all contribute to a responsive, tactile feel. Keep hover transitions between 150ms and 300ms — anything longer feels sluggish, anything shorter is barely noticeable.
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}For more complex shadow effects, the Box Shadow Generator lets you visually design layered shadows and export the CSS.
Page Transitions
In single-page applications and frameworks like Next.js, page transitions help maintain spatial context as users navigate. A common approach is to fade out the current page content and fade in the new content. The CSS View Transitions API, now supported in modern browsers, makes this even easier with built-in cross-fade capabilities.
Scroll-Triggered Animations
Elements that animate into view as the user scrolls create a sense of progressive disclosure. The modern approach uses the Intersection Observer API in JavaScript to add a class when an element enters the viewport, which triggers a CSS animation. The CSS itself stays simple — a fade-in or slide-up keyframe with animation-fill-mode: forwards. For the layout of these scrolling sections, tools like the CSS Grid Generator and CSS Flexbox Generator help you build the underlying structure quickly.
Using the Intellure CSS Animation Generator
Writing keyframes and animation properties by hand works fine for simple effects, but complex multi-step animations with custom timing functions become tedious to iterate on without a visual preview. The CSS Animation Generator on Intellure solves this by providing a visual interface where you can:
- Choose from presets: Start with common animations like fade, slide, bounce, spin, or pulse and customize from there.
- Configure every parameter: Adjust duration, delay, timing function, iteration count, direction, and fill mode with live controls.
- Preview in real time: See your animation play on a sample element as you adjust settings, without needing to refresh a browser tab.
- Export clean CSS: Copy production-ready
@keyframesandanimationcode directly into your project. - Custom cubic-bezier curves: Fine-tune easing with a visual bezier curve editor instead of guessing coordinate values.
Whether you are prototyping a micro-interaction or building a complex entrance sequence, starting with the generator saves significant time. It is completely free and runs entirely in your browser — no sign-up required.
Frequently Asked Questions
What is the difference between CSS transitions and CSS animations?
CSS transitions smoothly animate a property change between two states and require a trigger (like a hover or class change). CSS animations use @keyframes to define multi-step sequences that can run automatically, loop infinitely, and control motion at precise percentage points throughout the timeline. Use transitions for interactive state changes and animations for more complex or autonomous motion.
Which CSS properties are safe to animate for performance?
transform and opacity are the only two properties that are composited on the GPU in all modern browsers. Animating these avoids layout recalculations and repaints, resulting in smooth 60fps motion. Avoid animating width, height, top, left, margin, and padding whenever possible.
How do I make CSS animations accessible?
Use the @media (prefers-reduced-motion: reduce) media query to disable or simplify animations for users who have enabled reduced motion in their operating system settings. Replace motion-heavy animations with instant state changes or very short opacity fades. Never rely on animation alone to convey critical information.
What does animation-fill-mode: forwards do?
By default, an element reverts to its original styles after an animation completes. Setting animation-fill-mode: forwards tells the browser to keep the element in the state defined by the last keyframe (100% or to). This is essential for entrance animations where you want the element to remain visible after fading in, rather than snapping back to its initial hidden state.
Can I combine multiple CSS animations on one element?
Yes. Separate multiple animations with commas in the animation shorthand property — for example, animation: fadeIn 0.5s ease forwards, slideUp 0.8s ease-out forwards. Each animation can have its own duration, timing function, delay, and other settings. Be mindful of conflicting properties — if two animations both target transform, the last one in the list wins.
Try These Free Tools
Related Articles
CSS Grid vs Flexbox: When to Use Each Layout System
A practical comparison of CSS Grid and Flexbox — when to use each, how they differ, and how to combine them for real-world layouts.
10 CSS Tricks Every Web Developer Should Know in 2026
Master 10 powerful CSS techniques — gradient text, clamp(), container queries, scroll snap, CSS nesting, glassmorphism, and more with copy-paste code examples.
PX vs REM vs EM: Which CSS Unit Should You Use?
A practical guide to CSS units — what px, rem, and em actually do, when to use each one, and why modern development prefers rem for accessible, scalable layouts.