CSS Animation Tutorial: Complete Guide with Examples (2026)
CSS Animations vs JavaScript Animations in 2026
CSS animations have reached a level of capability in 2026 that makes JavaScript animation libraries unnecessary for the majority of UI animations. The Web Animations API bridges the gap for complex animations that previously required JavaScript, while CSS handles transitions, keyframe animations, scroll-driven animations, and view transitions natively. The performance advantage of CSS animations — they run on the compositor thread without touching the main thread — makes them the preferred choice for UI micro-animations, hover effects, entrance animations, and continuous decorative motion.
CSS Transitions: The Foundation
CSS transitions animate property changes triggered by state changes (hover, focus, class additions). The transition property defines which properties to animate, the duration, the easing function, and an optional delay. The most performant transitions animate only transform and opacity — these properties are composited by the browser without causing layout or paint recalculations. Animating width, height, padding, or margin triggers layout recalculation on every frame and produces janky animations at scale.
The easing function profoundly affects the feel of an animation. Linear easing feels mechanical. ease-in (slow start, fast end) feels like acceleration. ease-out (fast start, slow end) feels like deceleration — the most natural feel for elements entering or exiting the screen. cubic-bezier() enables custom easing curves. CSS in 2026 supports the linear() function for multi-point easing that approximates spring physics without JavaScript.
CSS Keyframe Animations
Keyframe animations (@keyframes) define multi-step animations that play automatically or on trigger. Define the keyframes with percentage-based steps (0%, 50%, 100%) or keywords (from, to), then apply the animation with the animation property specifying the keyframe name, duration, easing, delay, iteration count, and fill mode. animation-fill-mode: forwards preserves the final keyframe state after the animation completes — critical for entrance animations where you don't want elements snapping back to their starting state.
Scroll-Driven Animations
CSS scroll-driven animations are the most significant new CSS animation feature of the past two years, now baseline across all major browsers in 2026. Link any CSS animation to scroll position using animation-timeline: scroll() or animation-timeline: view(). Elements can animate based on the page scroll position or their own position within the viewport. Progress bars that fill as the user scrolls, elements that fade in as they enter the viewport, and parallax effects — all achievable with pure CSS, no JavaScript Intersection Observer required.
View Transitions API
The View Transitions API enables smooth animated transitions between page states or navigation events. In a single-page application, wrapping a state change with document.startViewTransition() automatically creates a cross-fade between the before and after states. With CSS customization using the ::view-transition-old and ::view-transition-new pseudo-elements, you can create sophisticated shared-element transitions — like an image expanding from a thumbnail to a full-size view — with minimal code. ProofMatcher's templates use CSS animations throughout — download free animated UI templates at proofmatcher.com.
Animation Properties in Detail
The animation shorthand combines several properties, and knowing each one prevents surprises. animation-duration and animation-timing-function control speed and easing. animation-delay postpones the start, and a negative delay starts the animation part-way through, which is handy for staggering several loaders so they do not move in sync. animation-iteration-count sets how many times it runs, with infinite for loops. animation-direction: alternate plays forwards then backwards, creating smooth pulsing effects without writing the reverse keyframes.
The property beginners most often miss is animation-fill-mode. By default an element snaps back to its original style when the animation ends. Setting animation-fill-mode: forwards keeps the final keyframe, and both also applies the first keyframe during a delay, which prevents a visible flash before an entrance animation starts.
steps() for Frame-by-Frame Effects
Not every animation should be smooth. The steps() timing function jumps between a fixed number of positions, which is perfect for sprite-sheet animations, typewriter effects, and ticking clocks. A typewriter effect, for example, animates the width of a monospace text container with steps(20) for a 20-character line, so each step reveals exactly one more character.
Animating Custom Properties with @property
Normally, CSS custom properties cannot be animated smoothly because the browser does not know what type of value they hold. Registering a property with @property fixes that. Declare its syntax, for example syntax: "<angle>", an initial value, and whether it inherits. Once registered, a variable such as --angle can be transitioned or used in keyframes, which makes effects like rotating conic-gradient borders and smoothly shifting gradients possible in pure CSS.
Controlling CSS Animations from JavaScript
You can keep animations in CSS and still control them from scripts. Toggle a class to start an animation, listen for the animationend and transitionend events to run code when it finishes, and pause or resume with animation-play-state. For animations that depend on runtime values, the Web Animations API offers element.animate(), which takes keyframes and timing options, runs with the same performance as CSS animations, and returns an object you can pause, reverse, or await with its finished promise.
Accessibility Rules for Motion
Motion can make interfaces clearer, but it can also cause real discomfort. Wrap non-essential animations in a @media (prefers-reduced-motion: reduce) query and replace large movements with simple fades or no animation. The WCAG guidelines also require that anything moving, blinking, or scrolling automatically for more than five seconds, alongside other content, can be paused, stopped, or hidden by the user. Never flash content more than three times per second, because rapid flashing can trigger seizures.
Debugging and Performance
Browser developer tools include an Animations panel that records animations on the page, lets you slow them down, and shows each keyframe's timing, which is invaluable when an easing curve does not feel right. Use the Performance panel to check that animations do not cause layout or paint work on every frame; if they do, switch to transform and opacity. Keep infinite animations to a minimum, pause them when they are off screen, and avoid animating large blurred or shadowed areas. With these tools and habits, CSS alone can deliver polished, accessible motion for almost any interface. You can find ready-made CSS animations in the ProofMatcher component library.
Entry Animations with @starting-style
Transitions have always had one gap: they cannot animate an element the moment it appears, because there is no previous style to transition from. The @starting-style rule, now supported in all major browsers, fills this gap. Inside it you define the styles an element should start from when it is first rendered, and the browser transitions from those values to the normal ones. Combined with transition-behavior: allow-discrete, which lets the display property participate in transitions, you can fade and slide popovers, dialogs, and menus in and out with pure CSS:
.popover { opacity: 1; transform: translateY(0);
transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete; }
@starting-style { .popover { opacity: 0; transform: translateY(8px); } }
This replaces many small JavaScript helpers that previously toggled classes on the next animation frame just to trigger an entrance animation.
Custom Easing with linear()
The cubic-bezier() function can only describe a single smooth curve, which makes bounces and springs impossible. The newer linear() easing function accepts a list of points, so it can approximate any curve, including spring physics with overshoot and settle. Online generators turn spring settings into a linear() value you can paste into your CSS, giving you the natural feel of JavaScript spring animations while keeping the animation on the compositor. Browsers that do not support it simply fall back to the previous easing value if you declare one first.