Blog/CSS Animations: A Practical Guide to Keyframes and Transitions (2026)
CSS

CSS Animations: A Practical Guide to Keyframes and Transitions (2026)

Vikas Sharma

Vikas Sharma

Senior Frontend Developer

June 27, 2026•12 min read
CSS animation timeline showing keyframes, easing curves and transition states

Motion is what makes an interface feel alive: a button that responds, a menu that slides, a loader that reassures. CSS handles most of it without a single line of JavaScript, through two tools that get confused constantly, transitions and animations. This guide untangles them, shows you which properties to animate for buttery-smooth results, and covers the easing and accessibility details that turn motion from gimmicky into polished.

Transition vs animation: which do you need?

A transition animates a property from its current value to a new one in response to a change, usually a hover, focus, or class toggle. It needs a trigger and it runs once, A to B. An animation, defined with @keyframes, runs on its own, can loop forever, and can pass through many intermediate states. Reach for a transition for state changes; reach for an animation for anything ongoing or multi-step.

TransitionAnimation
Needs a triggerYes (hover, class change)No, can autoplay
Can loopNoYes (infinite)
Intermediate stepsNo, just start and endYes, any number of keyframes
Best forHover, focus, togglesLoaders, attention, entrances

Transitions in practice

A transition needs three things: a property to watch, a duration, and ideally an easing curve. Declare it on the resting state, not the hover state, so it animates in both directions.

css
.button {
  background: #1B4DFF;
  transform: translateY(0);
  transition: transform 0.2s ease, background 0.2s ease;
}

.button:hover {
  transform: translateY(-2px);
  background: #163fd6;
}

Avoid transition: all. It looks convenient but forces the browser to watch every animatable property, which can trigger surprise animations and hurt performance. List the properties you actually change.

Writing @keyframes animations

An animation has two parts: a @keyframes block defining the stages, and an animation property applying it to an element.

css
@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card {
  animation: fade-in-up 0.5s ease-out both;
}

from and to are shorthand for 0% and 100%. For multi-step motion, use percentages. The both keyword (a fill-mode) keeps the element at its first frame before the animation starts and its last frame after it ends, preventing the flash you get otherwise.

css
@keyframes pulse {
  0%   { transform: scale(1);    opacity: 1; }
  50%  { transform: scale(1.08); opacity: 0.7; }
  100% { transform: scale(1);    opacity: 1; }
}

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

The only properties you should animate

This is the most important performance rule in CSS animation. The browser renders in stages: layout, paint, composite. Animating a property that triggers layout (like width, height, top, margin) forces the browser to recalculate the position of everything on every frame, which drops you below 60fps on anything but a trivial page.

  • transform (translate, scale, rotate) - composited, cheap, animate freely
  • opacity - composited, cheap, animate freely
  • Avoid: width, height, top, left, margin, padding - these trigger layout
  • Avoid animating box-shadow and background directly - they trigger paint every frame; animate a layered pseudo-element opacity instead

Want to move something? Use transform: translateX() instead of left. Want it bigger? transform: scale() instead of width. Ninety percent of smooth web animation is just transform and opacity.

Easing: the difference between cheap and premium

Linear motion (a constant speed) looks robotic because nothing in the real world moves that way. Easing curves add acceleration and deceleration. The built-in keywords cover most needs:

  • ease-out - fast start, slow finish; the best default for things entering or responding to a user
  • ease-in - slow start, fast finish; good for things leaving the screen
  • ease-in-out - slow at both ends; natural for looping motion
  • cubic-bezier(...) - custom curves when you want a specific feel, including subtle overshoot for a bouncy snap

For UI that responds to a user, ease-out almost always feels right: the element reacts instantly then settles. A custom cubic-bezier(0.34, 1.56, 0.64, 1) adds a touch of overshoot that reads as playful and modern.

Respect reduced motion

Some users get motion sickness or distraction from animation and set a system preference for reduced motion. Honoring it is an accessibility requirement, not a nice-to-have. Wrap non-essential motion in a media query:

css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

This near-instant override keeps your interface functional while removing the motion for users who asked not to see it.

Hand-writing @keyframes and dialing in easing curves is slow. The CSS Animation Generator lets you build keyframe animations visually, preview them live, and tweak timing and easing, then copies ready-to-use code into your stylesheet.

Conclusion

Use transitions for state changes and @keyframes for ongoing or multi-step motion. Animate transform and opacity and almost nothing else. Pick ease-out for responsive UI, list your transition properties explicitly, and always honor prefers-reduced-motion. Follow those rules and your motion will feel fast, intentional, and accessible instead of janky and decorative.

Table of Contents

Share Article

Vikas Sharma

Vikas Sharma

Senior Frontend Developer

Senior Frontend Developer with 8+ years of experience building production CSS layouts.