CSS vs GSAP for SVG: A Decision Guide
Use CSS for small, state-based SVG effects with fixed values. Choose GSAP when motion needs runtime values, coordinated sequencing, reusable playback controls, or lifecycle cleanup. Performance depends more on the properties and workload you animate than on the label “CSS” or “JavaScript.”
Use CSS for small, state-based SVG effects with fixed values. Choose GSAP when motion needs runtime values, coordinated sequencing, reusable playback controls, or lifecycle cleanup. Performance depends more on the properties and workload you animate than on the label “CSS” or “JavaScript.”
This is not a ladder where CSS is the beginner option and GSAP is the professional one. They solve different control problems. The best implementation is the smallest one your team can operate safely through loading, interaction, responsive changes, reduced-motion preferences, rerenders, tests, and future edits.
The quick CSS vs GSAP decision
- Choose CSS when one state changes into another, the values are known in advance, and the browser can own playback.
- Keep CSS and add the Web Animations API when the effect is still small but needs play, pause, reverse, or completion handling from JavaScript.
- Choose GSAP when several parts share a playhead, values come from runtime geometry or input, or the animation must be scrubbed, retargeted, interrupted, replayed, or cleaned up as a unit.
- Use both at component boundaries when CSS owns ordinary interface states and GSAP owns a separate choreographed scene. Do not let both systems write the same property on the same element at the same time.
A CSS-only interaction such as this staggered feature-card icon is a good example of complexity that does not automatically justify a library. For the GSAP mental model, start with the site’s intro to GSAP for SVG animation.
One SVG effect implemented both ways
Use the same accessible SVG for both examples. The dot rises eight units, becomes fully opaque, and returns to rest. It uses a fixed distance, one target, one duration, and no interactive controls, so CSS is the sensible default.
<svg class="choice-demo" viewBox="0 0 160 80"
role="img" aria-labelledby="choice-title choice-desc">
<title id="choice-title">Status dot motion demo</title>
<desc id="choice-desc">A status dot rises briefly and returns.</desc>
<rect x="20" y="38" width="120" height="4" rx="2"
fill="#d7dce2"></rect>
<circle class="choice-dot" cx="80" cy="40" r="10"
fill="#6d5dfc"></circle>
</svg>CSS implementation
The static state is the default. Motion is added only when the user has not requested reduced motion. That also leaves a usable result if the media query or animation never runs.
.choice-dot {
opacity: 0.65;
transform-box: fill-box;
transform-origin: center;
}
@media (prefers-reduced-motion: no-preference) {
.choice-dot {
animation: choice-dot-lift 700ms linear 2 alternate;
}
}
@keyframes choice-dot-lift {
from {
opacity: 0.65;
transform: translateY(0);
}
to {
opacity: 1;
transform: translateY(-8px);
}
}GSAP implementation
This produces the same visible states and linear timing. It is more code for this isolated effect, but the returned animation could later join a timeline or gain playback controls. The current GSAP documentation is version 3.15.
const media = gsap.matchMedia();
media.add("(prefers-reduced-motion: no-preference)", () => {
gsap.fromTo(
".choice-dot",
{ y: 0, opacity: 0.65 },
{
y: -8,
opacity: 1,
duration: 0.7,
ease: "none",
repeat: 1,
yoyo: true
}
);
});Use either the CSS animation or the GSAP tween, not both. In a component, retain the match-media instance and call its revert() method when the component is removed.
Compare the tools by production constraint
Interaction complexity and dynamic values
CSS transitions map naturally to states such as hover, focus, active, expanded, loading, and selected. Keyframes work well when a repeatable sequence has known values. CSS custom properties can make those values configurable, so “dynamic” does not always mean “use a library.” A small JavaScript handler can set a custom property while CSS continues to interpolate it.
GSAP becomes useful when destination values must be calculated repeatedly from pointer position, SVG geometry, responsive measurements, application data, or another live signal. It supports function-based values and invalidation; its quickTo() method is designed for repeatedly redirecting one numeric property, such as a pointer follower. That is a different requirement from toggling a class once.
Decision: prefer CSS for finite named states. Prefer GSAP when runtime values and interruptions are central to the interaction rather than an occasional setup detail.
Sequencing and playback controls
Two or three fixed CSS animations can be coordinated with delays. The maintenance cost rises when changing one duration forces several delays to be recalculated, or when the sequence needs overlaps, labels, nested scenes, reverse playback, or seeking.
A GSAP timeline is a container for tweens and other timelines. Its children share a playhead, while position parameters and labels describe sequence relationships. The complete scene can then be paused, resumed, restarted, reversed, or moved to a particular time. Choose that control model when the requirements actually mention those verbs.
Do not overlook the native middle path. The Web Animations API returns an Animation object with playback methods and a completion promise. It can be enough when a small effect only needs imperative controls.
const dot = document.querySelector(".choice-dot");
const animation = dot.animate(
[
{ transform: "translateY(0)", opacity: 0.65 },
{ transform: "translateY(-8px)", opacity: 1 }
],
{
duration: 700,
iterations: 2,
direction: "alternate",
easing: "linear"
}
);
animation.pause();
animation.currentTime = 350;
animation.play();Decision: do not add GSAP solely because JavaScript must start an animation. Add it when its sequencing, retargeting, plugin, or lifecycle model removes meaningful complexity.
Component lifecycle and responsive behavior
Declarative CSS rules do not create timeline instances, but any JavaScript used to add classes or event listeners still needs cleanup. GSAP can create animations, inline values, ScrollTriggers, and callbacks, so ownership must be explicit in component-based applications.
gsap.context() collects animations for scoped cleanup. gsap.matchMedia() associates setup with media conditions and reverts recorded animations when those conditions change. A mount function should return one teardown function:
export function mountMotion(root) {
const media = gsap.matchMedia(root);
media.add(
{
fullMotion: "(prefers-reduced-motion: no-preference)",
desktop: "(min-width: 48rem)"
},
(context) => {
const { fullMotion, desktop } = context.conditions;
if (!fullMotion) {
return;
}
const timeline = gsap.timeline({ paused: true })
.addLabel("start")
.to(".choice-dot", {
x: desktop ? 48 : 24,
duration: 0.4
}, "start")
.to(".choice-label", {
opacity: 1,
duration: 0.2
}, "start");
const replayButton = root.querySelector("[data-replay]");
const replay = () => timeline.restart();
replayButton.addEventListener("click", replay);
return () => {
replayButton.removeEventListener("click", replay);
};
}
);
return () => media.revert();
}Decision: if your framework can mount the same view more than once, require an owner, a scope, and an idempotent cleanup path regardless of animation technology.
Bundle, caching, and team skills
CSS already shipped in an existing stylesheet adds no animation-library JavaScript. GSAP introduces executable code, but the right comparison is the production bundle you actually deliver, not an isolated headline size. Its installation guide provides minified, UMD, and ES module builds. Import only the core and plugins you use, and explicitly register plugins so a build tool does not remove them during tree shaking.
A cached file is not free on a first visit. Measure cold navigation and repeat navigation separately. For self-hosted bundles, versioned or hashed URLs plus appropriate cache headers let long-lived caches work safely; MDN explains that pattern in its HTTP caching guide. Avoid assuming a third-party URL is already cached across unrelated sites.
Licensing information in older comparisons may also be stale. Webflow announced in April 2025 that GSAP and the previously paid plugins became free, including under the expanded commercial-use license. Delivery, upgrades, review, and team knowledge remain real costs even when the license fee is zero.
Decision: choose the tool your team can review and maintain, then enforce one shared pattern. A familiar CSS transition is cheaper than an unnecessary dependency; a well-owned timeline is cheaper than a maze of fragile delays.
Accessibility and reduced motion
Accessibility is not a point awarded to either technology. CSS and GSAP can both respect the same preference, and both can ignore it. The W3C’s guidance for Animation from Interactions says non-essential motion triggered by interaction should be disableable. Its CSS technique recommends prefers-reduced-motion.
Start from a meaningful static state and add motion under no-preference, as the CSS example does. In GSAP, evaluate the preference before constructing a motion-heavy timeline, or use gsap.matchMedia() so changes are handled and reverted. A calmer opacity or color response can preserve feedback, but it should still be reviewed with the design’s purpose in mind.
Reduced motion is not the only requirement. Under the conditions described by WCAG’s Pause, Stop, Hide guidance, automatically starting motion that lasts more than five seconds and appears alongside other content needs a way to pause, stop, or hide it unless it is essential. A permanent decorative loop deserves more scrutiny than a short state transition.
Use the fuller reduced-motion guide when defining the project policy. Also verify that disabling animation does not leave essential text invisible or an interface stuck in its initial state.
Debugging and testing
CSS is easy to inspect when the effect is driven by a class or pseudo-class. Browser animation tools expose keyframes and timing, while CSS animation events can report starts, iterations, and completions. Name keyframes by purpose, keep trigger classes separate from visual classes, and avoid scattered delay arithmetic.
For GSAP, retain the timeline instead of creating anonymous tweens throughout event handlers. Add semantic labels, create it paused when practical, and inspect it with seek() or progress(). ScrollTrigger’s development markers can expose trigger positions, but remove them from production configuration.
Test states rather than watching a real-time demo. Verify the initial, middle, and final playhead positions; reduced-motion output; repeated mount and teardown; rapid retriggers; viewport changes; keyboard operation; and the no-JavaScript baseline. A GSAP builder can be exercised deterministically:
export function buildStatusTimeline(dot) {
return gsap.timeline({ paused: true })
.fromTo(
dot,
{ y: 0, opacity: 0.65 },
{ y: -8, opacity: 1, duration: 0.7, ease: "none" }
);
}
const timeline = buildStatusTimeline(dot);
timeline.progress(0.5);
const halfwayY = Number(gsap.getProperty(dot, "y"));
console.assert(Math.abs(halfwayY + 4) < 0.1);
timeline.revert();For native animations, prefer the animation’s events or finished promise over arbitrary sleep durations. Visual regression tests should capture the same fixed states in both normal and reduced-motion modes.
Performance and SVG-specific behavior
“CSS is faster” and “GSAP is faster” are both incomplete claims. MDN’s CSS and JavaScript animation comparison explains that CSS and requestAnimationFrame()-based animation can be close when both perform work on the main UI thread. The rendering work caused by the chosen property is often more important than the syntax that updates it.
MDN’s animation performance guide separates style calculation, layout, paint, and composition. Geometry-changing properties can trigger layout and paint, while transform and opacity can often be handled during composition. “Often” matters: large SVGs, filters, masks, stroke effects, many simultaneous nodes, and surrounding page work still need measurement.
SVG transforms also require an explicit coordinate model. In CSS, transform-box: fill-box can make an origin relative to the element’s own bounds; MDN documents the SVG transform-origin behavior. GSAP’s core CSS plugin provides transform aliases and normalizes SVG transform origins across browsers. A focused pattern such as the hamburger-to-close icon is a useful place to practice that discipline.
Profile the actual scene on a representative mobile device. Record frame stability, main-thread time, paint area, memory, concurrent animation count, cold-load bytes, and interaction latency. Test while the page is doing realistic work. A smooth circle in an empty benchmark does not settle the performance of a production illustration.
A practical decision framework
Choose CSS when the effect is a state
- The trigger is hover, focus, active, selected, expanded, loading, or a class change.
- Start, intermediate, and end values are known before playback.
- One or a few targets move independently.
- Playback does not need scrubbing, arbitrary seeking, or coordinated reversal.
- The static and reduced-motion states are easy to express declaratively.
The floating empty-state accents show how a restrained CSS loop can remain self-contained.
Choose GSAP when the effect is a system
- Multiple SVG parts require shared timing, labels, overlaps, or nested sequences.
- Values depend on measurements, data, pointer input, scroll position, or previous state.
- The product requires pause, reverse, seek, scrub, restart, or repeat controls.
- Animations must be created and reverted with component or route lifecycles.
- SVG-specific requirements justify plugins such as MotionPath, DrawSVG, or MorphSVG.
- A single timeline makes debugging and testing clearer than coordinated CSS delays.
Choose a hybrid when ownership stays clear
Let CSS own focus feedback, button states, simple icon transitions, and reduced-motion defaults. Let one GSAP module own the choreographed illustration. Keep shared tokens such as duration and easing documented, but never run competing CSS and GSAP animations on the same transform or opacity property. A staged effect such as a logo reveal is a useful boundary to evaluate.
Production migration checklist
- Inventory the current motion. Record each target, trigger, property, duration, delay, repeat, end state, and reduced-motion behavior.
- Write an equivalence contract. Define what must remain visually and functionally identical before changing technology.
- Preserve a static baseline. Content and controls must remain usable before JavaScript loads, after it fails, and when motion is reduced.
- Assign one owner per property. Remove the old CSS animation before GSAP writes that property, or keep the effect in CSS.
- Migrate one component. Do not replace a site-wide motion system in one release. Keep the change reversible.
- Create a lifecycle boundary. Mount from one function and return one cleanup function. Verify repeated mount, unmount, and breakpoint changes.
- Pin and audit the dependency. Use the approved GSAP 3.15.x release in the project lockfile, import only required plugins, and register them explicitly.
- Implement reduced motion before polish. Test the operating-system preference and any product-level pause control.
- Add deterministic checks. Assert initial, intermediate, and final states; rapid retriggers; reverse playback; teardown; and no-JavaScript behavior.
- Measure both delivery and rendering. Compare cold and warm loads, then profile the real animation under representative CPU and page activity.
- Roll out with a fallback. Watch errors and real-user performance, and retain a simple static or CSS state while confidence grows.
- Delete obsolete ownership. After validation, remove unused keyframes, listeners, imports, flags, and duplicate reduced-motion rules.
Frequently asked questions
Is CSS always faster than GSAP for SVG?
No. CSS can let the browser optimize simple animations effectively, but the changed properties and resulting layout, paint, composition, SVG complexity, and surrounding workload determine much of the cost. Compare the real implementations with browser performance tools instead of choosing from a library label.
When is GSAP worth adding for one SVG?
Add it when that SVG needs capabilities you would otherwise rebuild: a coordinated timeline, runtime measurements, frequent retargeting, scrubbing, reversible controls, reliable lifecycle cleanup, or a justified SVG plugin. A single fixed hover, entrance, or loop usually does not meet that threshold.
Can CSS and GSAP be used together?
Yes. A common production split is CSS for ordinary component states and GSAP for a separate story or illustration timeline. Problems begin when both systems simultaneously write the same transform, opacity, or SVG attribute. Document ownership at the element-and-property level.
Can the Web Animations API replace GSAP?
For some projects. It gives native keyframe effects programmatic playback, including pause, play, reverse, timing changes, and completion handling. GSAP remains useful when its timeline authoring, retargeting helpers, SVG behavior, plugins, or cleanup conventions remove more code than the dependency adds.
Does GSAP fix SVG transform-origin problems?
GSAP’s CSS plugin normalizes SVG transform origins across browsers and offers svgOrigin for a point in the SVG’s global coordinate space. CSS can also be reliable when transform-box, transform-origin, and the intended coordinate system are explicit. Test the browsers and SVG structure you support.
Should an existing CSS animation be migrated just because it may grow?
No. Migrate on observed requirements, not hypothetical complexity. Warning signs include cascading delay calculations, duplicated timelines, runtime geometry hacks, competing event handlers, missing cleanup, or tests that must wait for wall-clock timing. If the current CSS remains readable and complete, keeping it is a valid production decision.
The final rule
Choose by control surface, not prestige. CSS is the strong default for fixed, state-based SVG motion. The Web Animations API covers a useful native middle ground. GSAP earns its place when animation becomes coordinated application behavior with dynamic values, playback controls, and lifecycle ownership.
Whichever tool wins, preserve a meaningful static state, respect motion preferences, assign one owner to each animated property, and measure the actual scene. Those decisions matter more than the logo on the animation engine. For broader context, see why SVG animation works well on modern websites.
Sources and further reading
- GSAP documentation home
- GSAP Timeline
- GSAP CSSPlugin
- gsap.context()
- gsap.matchMedia()
- GSAP installation
- gsap.quickTo()
- Using CSS animations
- Using the Web Animations API
- CSS and JavaScript animation performance
- Animation performance and frame rate
- SVG transform-origin
- HTTP caching
- WCAG Animation from Interactions
- Technique C39: prefers-reduced-motion
- WCAG Pause, Stop, Hide
- GSAP becomes free
Related articles
SVG Animation Not Working? A Production Debugging Playbook
Fix broken SVG animations with a systematic checklist for embedding, CSS, JavaScript, paths, IDs, reduced motion, browser timing, and performance.