DEV Community

Cover image for Animated SVG on a product page, without Lottie
Naved Alam
Naved Alam

Posted on

Animated SVG on a product page, without Lottie

A lot of product pages have one small moving illustration near a feature: a queue draining, a cache filling, a chart ticking up. It's usually a GIF, a video or a Lottie file. For most of these, an SVG with a few lines of CSS animation inside it is lighter, sharper and easier to keep on brand.

Here's how I add them, the three ways to put one on a page, and the two things people skip: reduced motion and pausing offscreen.

What "animated SVG" means here

An SVG file is code. It can carry its own <style> block, and CSS animations inside that block play wherever the SVG renders. No player script, no video element. It stays sharp at any size because it's vector, and it can use your site's colors.

A minimal example: three bars that rise in turn.

<svg viewBox="0 0 120 48" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="t">
  <title id="t">Three bars rising in turn</title>
  <style>
    .bar {
      fill: currentColor;
      transform-box: fill-box;
      transform-origin: bottom;
      animation: rise 1.4s ease-in-out infinite;
    }
    .b2 { animation-delay: 0.15s; }
    .b3 { animation-delay: 0.3s; }

    @keyframes rise {
      0%, 100% { transform: scaleY(0.4); }
      50%      { transform: scaleY(1); }
    }

    @media (prefers-reduced-motion: reduce) {
      .bar { animation: none; }
    }
  </style>
  <rect class="bar b1" x="10" y="8" width="24" height="40" rx="3" />
  <rect class="bar b2" x="48" y="8" width="24" height="40" rx="3" />
  <rect class="bar b3" x="86" y="8" width="24" height="40" rx="3" />
</svg>
Enter fullscreen mode Exit fullscreen mode

Two details worth copying:

  • transform-box: fill-box plus transform-origin: bottom makes each bar scale from its own bottom edge, not from the corner of the SVG.
  • The reduced-motion rule lives inside the file, so it goes wherever the file goes.

Three ways to put it on a page

1. As an image

<img src="/illustrations/bars.svg" alt="Three bars rising in turn" width="240" height="96" />
Enter fullscreen mode Exit fullscreen mode

This is the simplest. The CSS animation inside the file still plays. The trade-off: the page can't reach inside it. Your page CSS won't style it, currentColor won't pick up your text color, and you can't pause it from outside.

Use this when the illustration has fixed colors and you just need it to show up.

2. Inline in the HTML

Paste the <svg> markup straight into the page. Now it's part of the DOM:

  • currentColor follows the color of the parent element, so it matches light and dark themes for free.
  • CSS variables from your page work inside it.
  • Page scripts can pause it.

The cost is markup in your HTML, so keep the SVG clean (no editor metadata, no hidden layers).

3. As a React component

Same as inline, wrapped in a component:

export function Bars(props) {
  return (
    <svg viewBox="0 0 120 48" role="img" aria-label="Three bars rising in turn" {...props}>
      <style>{`
        .bars-bar {
          fill: currentColor;
          transform-box: fill-box;
          transform-origin: bottom;
          animation: bars-rise 1.4s ease-in-out infinite;
        }
        .bars-b2 { animation-delay: 0.15s; }
        .bars-b3 { animation-delay: 0.3s; }
        @keyframes bars-rise {
          0%, 100% { transform: scaleY(0.4); }
          50%      { transform: scaleY(1); }
        }
        @media (prefers-reduced-motion: reduce) {
          .bars-bar { animation: none; }
        }
      `}</style>
      <rect className="bars-bar" x="10" y="8" width="24" height="40" rx="3" />
      <rect className="bars-bar bars-b2" x="48" y="8" width="24" height="40" rx="3" />
      <rect className="bars-bar bars-b3" x="86" y="8" width="24" height="40" rx="3" />
    </svg>
  );
}
Enter fullscreen mode Exit fullscreen mode

One gotcha: a <style> inside an inline SVG is global to the page. If two illustrations both define .bar or @keyframes rise, they collide. Prefix class names and keyframe names per illustration, as above.

Keep it on brand with currentColor and variables

Inline SVG can read your design tokens:

.bars-bar  { fill: var(--illo-ink, currentColor); }
.bars-b3   { fill: var(--illo-accent, #14b8a6); }
Enter fullscreen mode Exit fullscreen mode
:root            { --illo-ink: #0f172a; --illo-accent: #0d9488; }
[data-theme=dark] { --illo-ink: #e2e8f0; --illo-accent: #2dd4bf; }
Enter fullscreen mode Exit fullscreen mode

Switch the theme and the illustration follows, with no second file for dark mode.

Respect reduced motion

The @media (prefers-reduced-motion: reduce) block in the examples turns the animation off for people who asked their OS for less motion. Make sure the still frame still makes sense: pick keyframes where the resting state (here, animation: none leaves the bars at full height) reads as a finished picture.

Pause it when it scrolls away

An illustration halfway down the page doesn't need to animate while nobody can see it. An IntersectionObserver can toggle a class:

.is-paused,
.is-paused * {
  animation-play-state: paused;
}
Enter fullscreen mode Exit fullscreen mode
import { useEffect, useRef } from "react";

export function usePauseOffscreen() {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(([entry]) => {
      el.classList.toggle("is-paused", !entry.isIntersecting);
    });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return ref;
}

// usage
// const ref = usePauseOffscreen();
// <Bars ref={ref} />   (forward the ref, or wrap Bars in a <div ref={ref}>)
Enter fullscreen mode Exit fullscreen mode

This only works for inline SVG. An <img> is a closed box to the page.

SVG or Lottie?

Lottie is good for long, character-style animation exported from After Effects. For a small explanatory loop next to a feature, CSS-animated SVG wins on the basics: no player script on the page, plain text you can diff in a pull request, and colors that follow your theme.

If you'd rather not build these by hand

Disclosure: I make Illos, a library of animated SVG illustrations for product sites. Each one works the way this post describes: CSS animation inside the file, React or plain SVG, light and dark, reduced motion handled. There's a free set you can use in commercial work with no account and no credit needed, and Pro adds skills so Claude Code, Cursor, Codex or any coding agent can place illustrations in your codebase in the same style.

Questions about any of the code above are welcome in the comments.

Top comments (0)