Your First SVG Animation with CSS
To animate SVG with CSS, place the SVG inline, give the part you want to move a stable class, then apply a transition for a two-state interaction or @keyframes for a timed sequence. Start with the complete static graphic so it still works without motion or JavaScript.
This tutorial builds one small launch-icon animation with inline SVG and CSS. The finished icon scales with its container, inherits the surrounding text color, responds to pointer and keyboard input, and remains a complete static graphic when motion is unavailable or unwanted.
CSS is a good first tool when the effect has only a few states and does not need a controllable timeline. If the idea already involves several precisely sequenced scenes, read CSS vs GSAP for SVG before forcing all of that choreography into keyframes.
Choose inline SVG for part-by-part animation
An SVG used through <img src="icon.svg"> is useful when the page only needs to display or move the image as one object. The page stylesheet cannot select a path inside that image. Put the SVG markup directly in the HTML when individual circles, paths, or groups need their own classes and motion. MDN’s guide to including vector graphics in HTML explains that distinction.
Inline SVG does add markup to the document, so do not inline every repeated illustration automatically. Use it when direct styling or interaction is genuinely useful. For this first project, it gives us the clearest relationship between the graphic and its CSS.
Build the complete static state first
Start with a real link and an icon that already looks finished before any animation runs:
<a class="launch-card launch-card--intro" href="/svg-animation-examples">
<span class="launch-card__label">Explore animation examples</span>
<svg
class="launch-card__icon"
viewBox="0 0 64 64"
aria-hidden="true"
>
<circle
class="launch-card__orbit"
cx="32"
cy="32"
r="26"
></circle>
<g class="launch-card__rocket">
<path
class="launch-card__body"
d="M32 10c8 6 13 15 13 25l-13 10-13-10c0-10 5-19 13-25Z"
></path>
<circle
class="launch-card__window"
cx="32"
cy="26"
r="4"
></circle>
<path
class="launch-card__flame"
d="M27 43 32 55 37 43"
></path>
</g>
</svg>
</a>The outer <svg> creates the SVG viewport. Its viewBox="0 0 64 64" defines an internal coordinate system whose origin is 0,0 and whose width and height are 64 units. CSS can display that coordinate system at many sizes without rewriting the path data. MDN’s viewBox reference describes the four values as minimum x, minimum y, width, and height.
The <g> groups the rocket parts so one transform can move them together. The circle and paths draw the visible shapes. Descriptive component classes such as launch-card__rocket are deliberate animation hooks; they are easier to preserve than generated IDs such as path1234 after a design export changes.
Decide whether the SVG is decorative or meaningful
Accessibility depends on the graphic’s purpose, not its file type. In this example, the visible link text already communicates the action. The icon adds decoration, so aria-hidden="true" keeps it from repeating the link’s name.
A standalone diagram that contributes information needs an accessible name instead. One compact pattern is:
<svg
class="upload-status"
viewBox="0 0 48 48"
role="img"
aria-labelledby="upload-title upload-description"
>
<title id="upload-title">Upload complete</title>
<desc id="upload-description">
A checkmark inside a circle.
</desc>
<circle cx="24" cy="24" r="19"></circle>
<path d="m15 24 6 6 13-14"></path>
</svg>Keep those IDs unique if the graphic appears more than once. For a complex chart or diagram, provide the essential explanation as visible text as well. The W3C images accessibility tutorial distinguishes decorative, informative, functional, and complex images. The site’s accessible SVG guide goes deeper into <title>, <desc>, and aria-hidden.
Add responsive color and sizing
Now style the static component. No animation properties are needed yet:
.launch-card {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
color: #174ea6;
background: #ffffff;
border: 2px solid currentColor;
border-radius: 0.75rem;
text-decoration: none;
}
.launch-card__icon {
display: block;
width: clamp(2.75rem, 12vw, 4rem);
height: auto;
flex: 0 0 auto;
}
.launch-card__orbit {
fill: none;
stroke: currentColor;
stroke-width: 2;
opacity: 0.35;
}
.launch-card__body {
fill: currentColor;
}
.launch-card__window {
fill: #ffffff;
}
.launch-card__flame {
fill: none;
stroke: currentColor;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
opacity: 0.55;
}
.launch-card__rocket,
.launch-card__orbit {
transform-box: fill-box;
transform-origin: center;
}The viewBox supplies the aspect ratio, while CSS controls the rendered width. height: auto preserves that ratio. Test the result inside its real layout rather than assuming one fixed icon size.
currentColor lets SVG paint values use the computed CSS color. Change the link’s color for a theme or state and the border, rocket, orbit, and flame follow it. MDN documents how currentColor supplies SVG fill and stroke colors.
Use a transition for two states
A transition is the clearest choice when an existing state changes into one other state, such as rest to hover or keyboard focus. The browser interpolates the properties after the selector begins or stops matching. CSS keyframes are better when the motion needs named intermediate stages, runs automatically, or repeats. MDN makes the same distinction in its comparison of CSS transitions and animations.
Add a visible focus indicator for every keyboard user, then enable motion only for people who have not requested less of it:
.launch-card:focus-visible {
outline: 3px solid currentColor;
outline-offset: 3px;
}
@media (prefers-reduced-motion: no-preference) {
.launch-card__rocket,
.launch-card__flame {
transition:
transform 220ms ease-out,
opacity 220ms ease-out;
}
.launch-card:focus-visible .launch-card__rocket {
transform: translateY(-3px) rotate(3deg);
}
.launch-card:focus-visible .launch-card__flame {
opacity: 1;
transform: scaleY(1.12);
}
.launch-card:active .launch-card__rocket {
transform: translateY(1px) scale(0.96);
}
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
.launch-card:hover .launch-card__rocket {
transform: translateY(-3px) rotate(3deg);
}
.launch-card:hover .launch-card__flame {
opacity: 1;
transform: scaleY(1.12);
}
}Listing the animated properties is safer than transition: all, which may animate an unrelated future style change. The hover query avoids treating touch input as though it had a persistent hover state. Keyboard focus receives the same motion through :focus-visible, while the focus outline remains visible even when motion is reduced. See more patterns in hover animations for SVG icons.
Use keyframes for a short sequence
The modifier class in the HTML can also play a restrained, once-only introduction. Two keyframe rules give the rocket and orbit different intermediate states:
@keyframes rocket-arrive {
0% {
opacity: 0.4;
transform: translateY(7px) scale(0.94);
}
70% {
opacity: 1;
transform: translateY(-2px) scale(1.02);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes orbit-wake {
0% {
opacity: 0.15;
transform: scale(0.9);
}
100% {
opacity: 0.35;
transform: scale(1);
}
}
@media (prefers-reduced-motion: no-preference) {
.launch-card--intro .launch-card__rocket {
animation: rocket-arrive 650ms cubic-bezier(0.2, 0.8, 0.2, 1) 1;
}
.launch-card--intro .launch-card__orbit {
animation: orbit-wake 750ms ease-out 1;
}
}The final keyframes match the normal static CSS, so the icon does not jump when the animation releases control. The effect runs once instead of looping indefinitely. Use infinite only when continual motion has a real purpose and the interface provides any controls required for persistent motion.
Make SVG transforms pivot where you expect
A rotation that swings around the entire SVG usually has the wrong reference box. The initial value of transform-box is view-box, so percentages may relate to the SVG viewport. transform-box: fill-box makes the object’s own bounding box the reference, and transform-origin: center selects its center. MDN’s transform-box reference includes this exact SVG rotation use case.
Do not apply fill-box mechanically to every element. A gauge needle may need a pivot at one end, while several objects orbiting together may intentionally use the full viewBox. Inspect the intended pivot, select the appropriate box, and state the origin explicitly.
Treat reduced motion as the default architecture
The examples begin in a finished static state and place movement inside prefers-reduced-motion: no-preference. That is safer than hiding the icon first and relying on an animation to reveal it. If CSS is incomplete, a rule fails, or a user requests less motion, the useful end state is still present.
.motion-part {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: no-preference) {
.motion-part {
transition: transform 200ms ease-out;
}
.motion-trigger:hover .motion-part,
.motion-trigger:focus-visible .motion-part {
transform: translateY(-3px);
}
}The media feature reports a user preference to remove, reduce, or replace non-essential motion; it is not a performance setting. Test the static result as intentionally as the animated one. W3C Technique C39 demonstrates the same static-first query. For alternatives such as replacing travel with a restrained color change, read how to respect prefers-reduced-motion.
Keep the first animation responsive and efficient
Transforms and opacity are useful starting points because browsers can often handle them without layout or repainting surrounding content. That is not a promise that every complex SVG transform will be smooth. Filters, masks, huge path counts, large painted areas, and many simultaneous elements can still be expensive.
- Move a parent group when its children share the same motion.
- Prefer short, purposeful effects over permanent decorative loops.
- Animate transform and opacity first when they express the design.
- Use fill, stroke, and path effects when needed, but measure them on representative hardware.
- Do not add will-change everywhere. Extra layers use memory; add the hint only after profiling shows a benefit.
web.dev’s high-performance CSS animation guide recommends checking the rendering cost of properties beyond transform and opacity and using browser performance tools to find layout, paint, or dropped frames. The site’s SVG animation performance guide expands that into a production workflow.
Test and debug before shipping
- Confirm the target. Inspect the inline SVG and verify that the class in the CSS matches the intended path or group.
- Check computed styles. Confirm that animation-name is not none, the duration is greater than zero, and another rule is not overriding the transform.
- Test the pivot. If rotation or scale looks like an orbit, inspect transform-box and transform-origin.
- Test every input. Use a mouse, Tab to the link, activate it with the keyboard, and check a touch-sized viewport. The focus indicator must remain visible.
- Emulate reduced motion. Confirm that the icon is complete, the action remains clear, and no motion rule escaped the media query.
- Resize and zoom. Test narrow containers, larger text, and 200% browser zoom. Watch for clipping caused by artwork that extends beyond the viewBox.
- Disable JavaScript. The link, label, and static icon should still work because this CSS effect does not depend on a script-added class.
- Profile the real page. Record the animation on a lower-powered device or throttled environment instead of judging an isolated icon only on a fast desktop.
If a page stylesheet cannot select the path at all, check whether the SVG was loaded through an <img>. If the color will not follow the component, look for hard-coded fill or stroke declarations overriding currentColor. If a line-drawing example is the next goal, use the focused guide to how SVG line drawing works rather than guessing dash lengths.
Preserve the no-JavaScript fallback
This example needs no JavaScript: the link navigates normally, CSS supplies the interaction, and the icon is visible before motion is applied. If JavaScript later controls replay or application state, let it add an enhancement class instead of adding the only rule that reveals the SVG. A script failure should remove orchestration, not meaning or access to the destination.
Production checklist
- Inline the SVG only when descendant parts need page-level styling or interaction.
- Preserve a correct viewBox and use CSS for rendered size.
- Use descriptive, stable classes instead of exporter-generated selectors.
- Choose decorative, informative, or functional semantics before animation.
- Build the complete static state first.
- Use a transition for two states and keyframes for a timed sequence.
- Set the transform reference box and origin deliberately.
- Support hover, visible keyboard focus, and reduced motion.
- Test without JavaScript, at narrow widths, with zoom, and in multiple browsers.
- Profile the integrated page before adding performance hints.
Quick answers
Can CSS animate parts inside an SVG?
Yes, when those parts are in an inline SVG or the CSS is embedded inside the SVG document. Page CSS cannot reach into an SVG loaded as an <img>. Inline the markup when the page must target individual paths, shapes, or groups.
Should a beginner use a transition or keyframes?
Use a transition when an element moves between two states such as rest and hover. Use @keyframes when the effect needs intermediate stops, starts automatically, or follows a repeatable sequence. Neither is inherently faster; the animated properties and artwork determine the rendering cost.
Why does an SVG element rotate around the wrong point?
Its transform may be using the SVG viewport as the reference box. Set an intentional transform-box and transform-origin. fill-box with a centered origin is common for an individual icon part, but it is not correct for every design.
Does CSS SVG animation require JavaScript?
No. Hover, focus, active states, transitions, and keyframe sequences can all run with CSS. JavaScript becomes useful when application data, replay controls, scroll state, or more complex orchestration must decide when the animation changes.
How do I make the animation accessible?
Give meaningful SVGs an appropriate text alternative, hide redundant decorative graphics, keep controls native and keyboard-operable, preserve a visible focus indicator, avoid communicating through motion alone, and provide a complete reduced-motion state.
What should I animate next?
Adapt this component by changing one variable at a time: motion distance, duration, easing, or the targeted SVG group. Then browse SVG animation examples for a small interaction to reproduce, or return to Learn SVG Animation for the next focused guide.
Start with one clear state change
A useful first SVG animation is not the one with the most keyframes. It is the one whose static meaning, selectors, trigger, pivot, motion preference, and fallback you can explain. Begin with a complete inline graphic, add one deliberate CSS change, test it with real inputs, and expand only when the result remains clear.
Sources and further reading
- Including vector graphics in HTML
- SVG viewBox
- Styling — SVG 2
- Images Tutorial
- SVG title element
- Using CSS animations
- CSS and JavaScript animation performance
- transform-box
- SVG color and currentColor
- :focus-visible
- prefers-reduced-motion
- Technique C39: Using prefers-reduced-motion
- High-performance CSS animations
- Animation performance and frame rate
- Progressive enhancement
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.