CSS
    css animationview transitions apiscroll-driven animationsstarting-stylecss propertyweb motion

    CSS Animation & Motion in 2026

    A guide to the four CSS-native techniques replacing JavaScript animation libraries: view transitions, scroll-driven animation, @starting-style, and @property.

    Editor: Paul RadfordAug 21, 20269 min read

    Paul RadfordFull-Stack Developer & Editor. Paul is a full-stack developer and editor of template.tips. Articles here are AI-drafted and reviewed by Paul, who knows the code well enough to catch what's wrong and cut the hype.

    CSS Animation & Motion in 2026

    Why CSS animation looks different now

    For most of CSS history, animation meant one of two things: a transition on a property you already controlled, or a JavaScript library doing the math on every frame. Anything more ambitious, page transitions, scroll-linked reveals, elements that fade in from nothing, required either a framework or a pile of getBoundingClientRect() calls and manual timing.

    That gap is closing. Four separate CSS and browser features now cover most of what used to require JavaScript: the View Transitions API, scroll-driven animations, the @starting-style rule, and @property. None of them are exotic tricks. They are the missing primitives that make declarative motion actually work, and together they change what "just use CSS" can mean.

    This page is the map. Each section below explains what a technique is for, when it is the wrong tool, and links out to the full playbook if you need implementation detail. If you read nothing else, you should leave with a working sense of which of the four to reach for and why.

    The old way, and what it cost

    Before these features existed, most non-trivial motion followed the same pattern: attach a scroll or resize listener, read layout with getBoundingClientRect(), compute a delta, then apply a transform inside requestAnimationFrame. This is the FLIP technique (First, Last, Invert, Play), and it works, but every read of layout is a potential forced reflow, and every scroll handler runs on the main thread alongside your actual application code. Jank was the default outcome, not the exception.

    The four techniques below exist specifically to move that work off the main thread, or to remove the JavaScript entirely.

    Moving between states without hand-rolled FLIP

    The clearest case is page and state transitions. Historically, animating a card into a full page view, or animating between two views in a single-page app, meant capturing the "before" position and size with getBoundingClientRect(), capturing the "after" state, then interpolating between them yourself.

    The View Transitions API does this natively. For same-document transitions it takes one JavaScript call to tell the browser "this DOM update is a transition, capture before and after states and animate between them." For cross-document navigations, standard multi-page site navigation, it needs no JavaScript at all: the browser handles the capture and crossfade itself. Either way, you are no longer computing transforms by hand. You are describing the transition and letting the browser own the pixels.

    The trade-off is that you are handing control of a real-time animation to the browser's compositor, which means the customization surface is pseudo-elements and animation properties, not arbitrary per-frame logic. If you need a physics-based drag interaction or something driven by user input mid-gesture, this is the wrong layer, that still belongs to JavaScript. But for "navigate from A to B and animate the shared elements," it replaces a genuinely fiddly category of hand-written code. MDN's View Transition API reference is the place to check current browser support before you commit to it as your only implementation.

    Animating as the user scrolls, without a scroll listener

    Scroll-linked effects, parallax backgrounds, progress bars tied to reading position, elements that reveal as they enter the viewport, are common enough to be a category of their own, and for a decade they all ran the same way: a scroll event listener firing constantly, paired with requestAnimationFrame to throttle the resulting style writes to the main thread. It worked, but it competed for the same thread as everything else your page was doing, and on a busy page that competition showed up as visible stutter.

    Scroll-driven animations move that binding into CSS itself, so the animation timeline is driven by scroll position and computed off the main thread rather than recalculated on every scroll event. The practical effect is that parallax, scroll progress indicators, and reveal-on-scroll effects stop depending on your JavaScript execution budget. Scroll-Driven Animations: The Complete Playbook for 2026 walks through all three patterns in detail, including where the CSS approach has gaps versus a scroll listener and how to decide which pattern fits a given layout.

    This is worth pairing with MDN's scroll-driven animations documentation and web.dev's scroll-driven animations guide if you want to verify current syntax and browser coverage before shipping, since this is one of the newer entries in the group and support timelines matter more here than for the others.

    Giving new elements something to animate from

    Transitions need two points: a starting computed style and an ending one. That is fine for a hover state or a class toggle, the element already had a computed style a moment ago. It breaks down for anything that is freshly inserted into the DOM, a modal, a toast, a newly rendered list item, because a brand-new element has no previous computed style for the transition engine to reference. Without one, the element just appears in its final state. No fade, no scale, nothing to animate through.

    The @starting-style rule exists to solve exactly that gap. It lets you declare, in pure CSS, what the element's style should be treated as before it existed, giving the transition engine the missing starting point it needs. No JavaScript timing hack where you add a class one frame after mount, no setTimeout(0) workarounds. The @starting-style Rule: Triggering Entry Animations in Pure CSS covers the syntax and the cases where it does and does not apply, including how it interacts with the display: none to display: block transition problem that used to require its own separate workaround. MDN's @starting-style reference is a good second check on browser support, since entry animations are exactly the kind of thing that degrades badly (an element that just never appears) if a browser silently ignores the rule.

    Making custom properties actually animate

    Custom properties (the --variables you have probably used for theming) look like they should animate the same way any other CSS value does. They do not, by default. A CSS custom property is just an untyped string as far as the browser is concerned, until you explicitly register it. That means the browser has no idea whether --angle is an angle, a color, or a length, so it has no idea how to interpolate between two values of it. The visible symptom is a gradient color or rotation angle that snaps instantly from old to new instead of tweening through the frames in between, exactly the kind of animation that looks broken rather than merely absent.

    @property fixes this by letting you register a custom property with an explicit syntax (angle, color, length, and so on), an initial value, and whether it inherits. Once registered, the browser knows how to interpolate it and the animation works the way you'd expect from any built-in property. CSS @property: The Missing Piece for Smooth Property Animations covers the registration syntax and the specific cases, animated conic-gradient angles and multi-stop gradient colors are the two most common, where this makes a previously-impossible-in-pure-CSS effect suddenly straightforward. MDN's @property reference is the canonical source for the exact syntax values it accepts.

    How the four fit together

    These are not competing techniques, they solve different parts of the same problem and are frequently used in combination:

    • View transitions handle moving between two whole states (pages, views, major DOM swaps) and own the crossfade or shared-element motion between them.
    • Scroll-driven animations handle motion that should track scroll position continuously rather than fire once, off the main thread.
    • @starting-style handles the specific problem of animating something into existence when there is no prior state to transition from.
    • @property handles the specific problem of making a custom property, an angle, a gradient stop, anything the browser doesn't natively know how to interpolate, animate smoothly instead of snapping.

    A single interface can use all four at once: a modal that view-transitions into place, uses @starting-style for its own fade-and-scale entrance, sits inside a page that reveals sections on scroll, and uses a @property-registered custom property to animate a gradient border. None of that requires a general-purpose animation library. It requires knowing which of the four primitives applies to which part of the problem, which is the point of this page.

    The other thing worth saying plainly: all four are declarative. You describe the states or the timeline, and the browser's compositor does the interpolation. That is a meaningful difference from a JavaScript animation loop, not just a style preference, because compositor-driven animation is less likely to compete with your application's own main-thread work for frame budget.

    Who this is not for

    If you need physics, spring animations, drag-and-release with momentum, anything where the animation has to respond to input mid-gesture, none of these four techniques replace a JavaScript animation library. They are declarative and state-based, not simulation-based. Use them for state transitions, scroll-linked reveals, entry and exit motion, and property tweening, and keep something like a dedicated animation library for gesture-driven or physics-driven interaction.

    If your audience is on browsers that do not yet support the feature you want, check support before adopting it as your only code path. Coverage differs across the four, view transitions and @property are the more established of the group, scroll-driven animations and @starting-style are newer, so caniuse.com and the relevant MDN pages are worth a look for any project with a hard browser support requirement rather than a "modern browsers" one.

    And if you are building something where animation is decorative rather than functional, a marketing page with a couple of hover effects, you probably do not need any of the above. A plain transition on transform and opacity still covers that case with less to learn and less that can go wrong.

    Where to start

    If you are migrating a single-page app's route changes away from a custom fade-and-slide implementation, start with the View Transitions API. If your site has parallax or scroll-progress effects currently wired to a scroll listener, the scroll-driven animations playbook is the direct replacement. If you have modals or toasts that pop in without animating, @starting-style is the specific fix. And if you have ever tried to animate a custom property and watched it snap instead of tween, @property is the missing registration step.

    Each of those pages goes deeper than this one does on purpose. This page's job was to get you to the right one.

    Related Articles