Two web platform features landed cross-browser in 2025-2026 that replaced most of what we used JavaScript animation libraries for: View Transitions API and CSS scroll-driven animations. Both are native, both run on the compositor, both ship zero KB of JavaScript. We’ve removed framer-motion from three FH client sites this year because of them. Here’s what they do and where we use them.
View Transitions API: cross-page animation without a SPA
View Transitions let you animate between two states of the DOM. In single-page apps, the two states are two routes. The browser snapshots the old state, applies the new state, and animates between them. The animation is configured in CSS. For Next.js App Router projects, View Transitions integrate cleanly with the built-in navigation.
We use it for: list-to-detail transitions (a project card animating into the project page), tab content transitions, accordion-style expansion that previously needed JavaScript height animation. The result feels smooth and native; the code is shorter than the JavaScript equivalent.
/* Default cross-fade for every navigation */
@view-transition {
navigation: auto;
}
/* Custom animation for a specific element pair */
.project-card {
view-transition-name: project-hero;
}
.project-detail-hero {
view-transition-name: project-hero;
}
/* The animation runs automatically when the names match across nav */
::view-transition-old(project-hero),
::view-transition-new(project-hero) {
animation-duration: 220ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}Browser support
Chrome, Edge, Safari shipped View Transitions for same-page navigations in 2024. Cross-page View Transitions shipped in Chrome 126 (mid-2024) and Safari 17 (late 2024). Firefox is the laggard — partial support landed in early 2026. For SMB sites, the cross-browser support is good enough to ship; Firefox users get a normal cross-fade instead of the named-element transition.
Scroll-driven animations: native parallax and reveal
CSS scroll-driven animations let you tie an animation to scroll position. No JavaScript IntersectionObserver, no scroll-event listeners, no main-thread blocking — the animation runs on the compositor.
/* Fade in elements as they enter the viewport */
.fade-in-on-scroll {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}Browser support for scroll-driven animations
Chrome and Edge have full support. Safari 17+ has partial support (more landed in 17.5). Firefox: in development as of early 2026. For non-supporting browsers, the animation simply doesn’t play — the content is still visible. Progressive enhancement at its cleanest.
What we replaced with these features
- framer-motion `<motion.div initial="" animate="" exit="">` patterns → View Transitions for navigation, CSS keyframes for component-level animation.
- react-intersection-observer + setState chains for scroll-triggered fade-ins → CSS scroll-driven animations.
- JavaScript-driven parallax → scroll-driven CSS animations.
- Library-based page transitions (gsap, lenis) → View Transitions for navigation.
Bundle size impact
framer-motion is ~30-50KB gzipped (depending on which features you import). gsap is ~70KB. Removing them from a site dropped one FH client’s LCP by 280ms and Time-to-Interactive by 480ms. The animations look the same; the page loads faster.
When you still need a JavaScript library
- Gesture-based animations (drag-to-dismiss, swipeable cards). Native CSS doesn’t do gestures.
- Physics-based animations (spring, momentum). CSS easings don’t simulate physics.
- Complex orchestrated sequences with branching state. CSS keyframes get unwieldy past a point.
- Animations that need to dynamically respond to data values (e.g., a meter animating to whatever number).
When we do use a JS library in 2026, we reach for Motion One (the framer-motion successor without React-coupling) or `@react-spring/web` for physics. Both are smaller than framer-motion.
Reduced motion: the contract that ships with every site
Every animation respects `prefers-reduced-motion`. With CSS scroll-driven animations, the contract is trivial:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}Performance compared to JS-based animations
CSS animations on the compositor run at 60fps even when the main thread is busy. JS animations run on the main thread and stutter under load. On a low-end Android device loading a heavy page, the difference between CSS and JS animation is the difference between smooth and jank. View Transitions and scroll-driven animations are the higher-performance choice on every device.
How this lands across FH client work
Three FH client sites have migrated from framer-motion to native animation. New builds default to native. We use the FH motion contract (durations: 120/180/240/520ms; easing: cubic-bezier(0.16, 1, 0.3, 1)) which works cleanly in pure CSS. If your site is shipping framer-motion or similar in 2026, book a consultation — the migration is a one-day engagement that ships measurable performance gains.