← Back to blog
·7 min read

Animated SVG Icons: How to Make Icons Move (Draw, Morph, Transform)

TL;DR

  • Three techniques cover most animated SVG icons: stroke drawing, transforms on parts, and path morphing
  • pathLength="1" makes stroke drawing work without measuring paths
  • transform-box: fill-box fixes rotations that swing around the wrong point
  • Interactive, multi-state icons are in the animated icons collection

Animated SVG icons are icons that move using nothing but SVG and CSS: a checkmark that draws itself, a menu that morphs into a close button, a loader that traces a circle. They are tiny, crisp at any size, and need no runtime. This guide shows how to make SVG icons move, the three techniques that cover almost every case, and the mistakes that make them jump or blur.

Why animate icons as SVG?

  • Weight: an animated SVG icon is the icon plus a few lines of CSS. No library.
  • Sharpness: vectors stay crisp on every screen.
  • Control: every path is in the DOM, so CSS and JavaScript can reach it.
  • The limit is complexity. SVG plus CSS is excellent for line drawing, transforms and simple morphs, and awkward for character animation or multi-state logic. For those, see the comparison in Lottie vs Rive vs CSS for animated icons.

    Technique 1: stroke drawing

    Line icons can draw themselves. The trick is a dashed stroke with one dash as long as the path, offset by its full length, then animated to zero.

    <svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
      <path class="draw" pathLength="1" d="M5 13l4 4L19 7" />
    </svg>
    .draw {
      stroke-dasharray: 1;
      stroke-dashoffset: 1;
      animation: draw 600ms cubic-bezier(0.65, 0, 0.35, 1) forwards;
    }
    @keyframes draw { to { stroke-dashoffset: 0; } }

    pathLength="1" normalises the path, so 1 is always "the whole path" and you never need to measure it with getTotalLength(). Stagger several paths with animation-delay to draw an icon stroke by stroke.

    Technique 2: transforms on parts of the icon

    Give each part of the icon its own group and animate it separately: a bell's clapper, an envelope's flap, the hands of a clock.

    <svg viewBox="0 0 24 24" width="32" height="32" aria-hidden="true">
      <g class="lid"><path d="M4 8h16" /></g>
      <path d="M6 8l1 12h10l1-12" />
    </svg>
    .lid {
      transform-box: fill-box;
      transform-origin: 0% 100%;
      transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
    }
    button:hover .lid { transform: rotate(-25deg) translateY(-2px); }

    transform-box: fill-box makes transform-origin relative to the part itself instead of the whole SVG canvas. Without it, rotations swing around the top-left corner of the icon, which is the most common reason SVG icon animations look broken.

    Technique 3: morphing between shapes

    You can animate the d attribute between two paths with the same number and type of points. Chromium browsers and Firefox animate d in CSS (Safari does not yet, and simply jumps to the end shape):

    .menu-line {
      d: path("M4 12h16");
      transition: d 300ms ease;
    }
    .open .menu-line { d: path("M6 6l12 12"); }

    For the classic hamburger-to-close morph, animating three separate lines with transforms works in every browser, so prefer it over path morphing when Safari matters.

    Making SVG icons move on hover

    Trigger icon animations from the interactive element, and include keyboard focus:

    .icon-button:hover .draw,
    .icon-button:focus-visible .draw { animation: draw 500ms ease-out; }

    More ready-made hover effects (ring, wiggle, bounce, spin) are in icon hover animation with CSS.

    Animated SVG icons in React

    Inline the SVG as a component so you can target its parts, and switch classes with state:

    export function CheckIcon({ done }: { done: boolean }) {
      return (
        <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" strokeWidth={2} aria-hidden="true">
          <path className={done ? 'draw' : 'draw paused'} pathLength={1} d="M5 13l4 4L19 7" />
        </svg>
      )
    }

    If you use Lucide, the icons already render inline SVG with stroke="currentColor", so every technique here applies. See how to animate Lucide icons in React.

    Common mistakes

  • Rotations around the wrong point: set transform-box: fill-box and a transform-origin on the part you rotate.
  • Blurry icons mid-animation: avoid scaling tiny icons by large factors; scale to 1.2 at most, or design the icon at the larger size.
  • Animating `width` or `height`: use transform: scale() instead.
  • Forgetting reduced motion: wrap decorative loops in @media (prefers-reduced-motion: no-preference).
  • When to go beyond SVG

    When an icon needs squash and stretch, several states, or a different move per icon, an interactive runtime such as Rive does the work for you. The animated icons collection has hover icons, animated nav icons, a mute toggle and emoji reactions, each a complete React component you add with one prompt.