CSS animations are one of the most expressive tools in a web developer’s toolkit. When applied thoughtfully, they communicate state, guide attention, and make interfaces feel genuinely alive — all without a single line of JavaScript. This guide walks through how they work under the hood, how to write them correctly, and how to use them responsibly in production.
In this article
- What is a CSS animation?
- The @keyframes rule
- Key animation properties
- How browsers render animations
- Performance best practices
- Practical UI/UX applications
- Advanced applications
- Animations vs transitions
- Common animation patterns
- Browser support
- Resources & tools
- Key takeaways
1. What is a CSS Animation?
A CSS animation enables HTML elements to transition smoothly between style states over time — entirely within your stylesheet, with no JavaScript required. The browser manages all the interpolation (calculating intermediate values between states) and timing on your behalf.
At its core, every CSS animation is built from two components working in tandem:
- The
@keyframesrule — declares what changes and at which points during the animation cycle. - The
animationproperty — applied to a specific element, it controls how the animation plays: its duration, speed curve, number of repetitions, and so on.
A useful analogy: think of @keyframes as the script for a film, and the animation property as the director deciding how to shoot it — pacing, timing, whether to loop.
2. The @keyframes Rule
The @keyframes block is where you define the stages of your animation. You assign the block a name, then declare CSS properties at specific percentage points within the animation’s timeline.
CSS@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
/* Equivalent shorthand using 'from' and 'to' */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
For animations requiring more than two stops, use percentage values:
CSS@keyframes colourCycle {
0% { background-color: red; }
25% { background-color: yellow; }
50% { background-color: blue; }
100% { background-color: green; }
}
💡
When to use from/to vs percentages
Use from / to when you only need a start and end state. Use percentage stops when you need three or more stages. Both are valid — just be consistent within a single @keyframes block for readability.
3. Key Animation Properties
Once your @keyframes block is defined, you attach the animation to an element using the animation properties. Here is a complete example:
CSS.box {
animation-name: fadeIn; /* must match @keyframes name exactly */
animation-duration: 2s; /* required – default is 0s */
animation-timing-function: ease-in-out; /* speed curve */
animation-delay: 0.5s; /* wait before starting */
animation-iteration-count: 1; /* or 'infinite' */
animation-fill-mode: forwards; /* hold final state */
}
/* All of the above can be written as a single shorthand: */
.box {
animation: fadeIn 2s ease-in-out 0.5s 1 forwards;
}
animation-duration
Sets how long one complete cycle takes. Values such as 2s or 500ms are both valid. This property has no default value — omitting it means nothing will animate.
animation-timing-function
Controls the acceleration curve across the animation’s duration. Common values include ease (the default — slow start, fast middle, slow end), linear (constant speed, ideal for spinners), and cubic-bezier() for fully custom curves. Browser DevTools allows you to preview these curves visually before committing to code.
animation-iteration-count
Determines how many times the animation repeats. A value of 1 plays it once; infinite loops it continuously — useful for loading indicators and ambient background effects.
animation-fill-mode
Controls what happens before and after the animation runs. The most commonly needed value is forwards, which tells the element to retain the styles from its final keyframe once the animation finishes. Without it, animated elements snap back to their original styles at the end — a jarring experience for the user.
4. How Browsers Render Animations
Understanding the browser’s rendering pipeline is what separates developers who simply use CSS animations from those who use them well. The process follows four stages:
1
Parse CSS
Browser reads and stores your @keyframes definitions
2
Calculate Values
Interpolates property values between keyframe stops at ~60fps
3
Composite Layers
GPU-accelerated properties get their own layer — bypassing repaint
4
Paint & Display
The composited result is drawn to screen at 60fps (16ms per frame)
The critical insight from step three is that not all CSS properties are treated equally. Transform and opacity can be handed entirely to the GPU, which composites their layers without involving the CPU or triggering an expensive repaint. Properties such as width, top, or margin force the browser to recalculate the layout of the entire page — a process known as layout reflow.
5. Performance Best Practices
✓ GPU-Accelerated — Use These
transform(translate, scale, rotate)opacityfilter(with caveats)
✗ Avoid Animating These
width/heighttop/left/marginbox-shadow(very expensive)
To move an element horizontally, use transform: translateX(200px) rather than animating left: 200px. To change its apparent size, use transform: scale(1.05) rather than width and height. The visual result may look identical, but the performance difference on real devices is significant — the latter can drop frame rates from 60fps to single digits on complex pages.
♿
Accessibility: prefers-reduced-motion
Some users — particularly those with vestibular disorders — find motion on screen physically uncomfortable. The @media (prefers-reduced-motion: reduce) query allows you to disable or significantly reduce animations for those who have opted in to reduced motion in their operating system settings. This is a WCAG accessibility consideration, not an optional extra — build it from the start, not as an afterthought.
CSS/* Define the animation normally */
.spinner {
animation: spin 1s linear infinite;
}
/* Disable it for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
}
}
Additional tips: test your animations on mid-range mobile devices rather than a high-performance development machine. You can also use Chrome DevTools → Rendering → FPS Meter to see your frame rate in real time during development.
6. Practical UI/UX Applications
The most common everyday use cases for CSS animations fall into four categories:
Loading States
Spinning border loaders and pulsing skeleton screens communicate to the user that something is happening in the background, dramatically reducing perceived wait time. A skeleton screen — grey placeholder blocks that mimic page content — is far more reassuring than a blank white page.
Micro-interactions
Small, purposeful animations — a button that subtly scales on press, a heart icon that bounces when clicked, a checkbox that animates when ticked — confirm that user actions have been registered. These micro-moments make an interface feel responsive and alive rather than mechanical.
Page Transitions
Fading or sliding between views in a single-page application provides spatial context, helping users understand where they have navigated to and from. Without transitions, navigation between routes can feel abrupt and disorienting.
Attention Direction
A pulsing dot on a notification badge or a brief shake on an error message draws the eye precisely where it needs to go — without the user having to hunt for what changed on the screen.
📌
The golden rule of animation in interfaces
Every animation you add should serve a communicative purpose. If you cannot articulate why a particular motion helps the user understand something, it probably should not be there.
7. Advanced Applications
Responsive Animations
Beyond the prefers-reduced-motion consideration, animations can also be adapted to screen size using standard media queries — for example, disabling parallax effects on small screens where they tend to perform poorly.
Scroll-triggered Effects and Storytelling
Libraries such as AOS (Animate On Scroll) and GSAP attach CSS animations to scroll position, enabling landing pages to reveal content progressively as the user scrolls through a narrative. When done well, the animation itself carries part of the story.
Data Visualisation
Animating a bar chart as it grows to its final value, or a pie chart that assembles itself on load, draws the viewer’s eye and makes data feel dynamic rather than static. Libraries including D3.js and Chart.js both provide native support for this.
8. Animations vs CSS Transitions
CSS animations and CSS transitions both produce motion — but they solve different problems.
CSS Animations
- Multiple keyframe stages
- Can auto-play on page load
- Loop infinitely or a set number of times
- Complex, multi-step choreography
- Better for storytelling & looping effects
CSS Transitions
- Start and end state only
- Triggered by a state change (e.g. :hover)
- Simpler syntax
- Single-pass effect
- Better for interactive feedback
Rule of thumb: if the motion responds to a user action, reach for a transition. If the motion needs to run independently, loop, or involve multiple stages, use an animation. A loading spinner is an animation; a button colour change on hover is a transition.
9. Common Animation Patterns
The following six patterns account for the vast majority of animations you will encounter in production work:
Fade In / Out
Opacity 0→1. Ideal for modals, tooltips, and page transitions.
Slide In / Out
TranslateX or translateY. Used for sidebars, drawers, and notifications.
Bounce / Pulse
Scale with overshoot. Perfect for CTA buttons and notification badges.
Rotate / Spin
rotate(360deg) infinite linear. The classic loading spinner.
Scale / Zoom
scale(1.05) on hover. Gives image galleries a satisfying sense of depth.
Shake / Wobble
Rapid translateX oscillation. Communicates validation errors intuitively.
Here is an example of the shake pattern — particularly effective on form fields when a user attempts to submit without completing required inputs:
CSS@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-8px); }
40%, 80% { transform: translateX(8px); }
}
.input--error {
animation: shake 0.4s ease-in-out;
border-color: #c0392b;
}
10. Browser Support & Compatibility
CSS animations enjoy excellent browser support. Every modern browser — Chrome, Firefox, Safari, and Edge — implements the full specification without requiring vendor prefixes for standard properties. According to Can I Use, global coverage sits at 97–98%.
Vendor Prefixes
If you need to support Safari versions predating 2014, you may require -webkit- prefixed declarations. In modern workflows, tools such as Autoprefixer handle this automatically as part of a build pipeline, so it is rarely something you need to consider by hand.
Progressive Enhancement
For any critical content, always confirm it remains legible and functional with animations entirely disabled. An animation should enhance the experience, never be a requirement for it.
11. Resources & Tools
Learning Platforms
- 🔗 MDN Web Docs — CSS Animations — the definitive, always-current reference
- 🔗 CSS-Tricks Animation Almanac — practical explanations and examples
- 🔗 W3Schools CSS Animation Tutorial — beginner-friendly walkthrough
Tools & Libraries
- 🔗 Animate.css — a drop-in stylesheet of 80+ pre-built animations, excellent for prototyping
- 🔗 Animista — a visual animation generator; tweak parameters and it writes the CSS for you
- 🔗 Keyframes.app — a browser-based visual timeline editor for CSS animations
- 🔗 GSAP — the industry-standard JavaScript animation library for complex sequences
Testing & Performance
- 🔗 Chrome DevTools → Animations tab — scrub through animations frame-by-frame and adjust timing live
- 🔗 Chrome DevTools → Rendering → FPS Meter — monitor frame rate in real time
- 🔗 Lighthouse — audit animations for Cumulative Layout Shift and performance issues
- 🔗 Can I Use — CSS Animations — browser version compatibility at a glance
12. Key Takeaways
- 1CSS animations are native to the browser — for the majority of UI motion, no third-party library is required.
- 2Animate
transformandopacitywherever possible. Anything that triggers layout reflow will degrade performance on real devices, particularly on mobile. - 3
@keyframesdefines what changes; animation properties define how it plays. Keep them separate, clearly named, and purposeful. - 4
prefers-reduced-motionis an accessibility requirement, not a nice-to-have. Implement it from the outset of any animated feature. - 5Every animation needs a reason. Ask yourself whether the motion helps the user understand something — or whether it is simply decoration for its own sake.
Frequently Asked Questions
When should I use CSS animations rather than GSAP?
CSS animations handle the majority of UI motion well — loaders, page transitions, hover effects, and single-element choreography. GSAP becomes the better choice when you need precise timeline sequencing across many elements, scroll-triggered animations with complex orchestration, or SVG path morphing. GSAP also offers more consistent cross-browser behaviour for advanced use cases.
Do CSS animations affect SEO or Lighthouse scores?
Animations do not directly affect search engine rankings. They can, however, impact Lighthouse performance scores if they trigger layout reflow or cause Cumulative Layout Shift (CLS). Using transform and opacity exclusively, and respecting prefers-reduced-motion, will keep your scores clean.
Can SVG elements be animated with CSS?
Yes — any SVG attribute that maps to a CSS property (such as fill, stroke, opacity, and transform) can be animated using standard CSS @keyframes. For more complex path morphing, you would typically reach for SMIL animations or a JavaScript library such as GSAP.
What does animation-fill-mode: forwards actually do?
By default, once an animation completes, the element reverts to its original pre-animation styles. Setting animation-fill-mode: forwards instructs the browser to hold the element at whatever styles were declared in the final keyframe, keeping the end state visible after playback finishes.