Back

SVG Animated Wires

03/29/2026 6 min

I recently got into SVG animations after reading fuma-nama's article on how Clerk built their animated table of contents. The technique was simple but the result looked magical, so I decided to study it from scratch and build something with it.

This post walks through every concept I learned along the way, and ends with three interactive examples that combine all of them.

The SVG canvas

SVG is a vector format. Unlike images, it never blurs when you scale it. Every SVG starts with a coordinate system where (0,0) is the top-left corner, X grows to the right, and Y grows downward.

The viewBox attribute defines the internal coordinate space. The actual size on screen is controlled by CSS. This means you can design at any scale and it stays sharp.

<svg viewBox="0 0 200 100" class="w-full">
  <circle cx="100" cy="50" r="30" fill="none" stroke="gray" />
</svg>

Masks: the stencil trick

SVG masks work like stencils. You define a shape inside a <mask> element, and anything white in the mask is visible, anything black is hidden.

Think of it as cutting a hole in a piece of paper. The mask itself is invisible, it just controls what shows through.

<mask id="wire-mask">
  <line x1="25" y1="0" x2="25" y2="100"
        stroke="white" strokeWidth="2" />
</mask>
 
<rect fill="red" mask="url(#wire-mask)" />

The rectangle is 50px wide, but only the 2px where the line exists are visible. This is the core trick behind animated wires.

Animated wires in 3 steps

The recipe is simple:

A simple line — the shape of the wire.

When you add a linearGradient to the rectangle instead of a flat color, you get a trailing effect. The gradient fades from transparent to solid, creating the illusion of a spark with a tail.

Drawing curves with Path

The <path> element is the most powerful shape in SVG. Its d attribute accepts drawing commands:

For curved wires connecting two points, we use cubic Bezier. The control points create a smooth S-curve that flows naturally.

M 74 74 C 74 170, 260 74, 260 170

This draws a curve from (74, 74) to (260, 170). The first control point shares the X of the start, and the second shares the X of the end — that's what creates the S-shape.

The pathLength trick

Every SVG path has a real length in pixels, which varies depending on the curves. Instead of calculating it, you can normalize any path to whatever number you want with pathLength.

<path d="..." pathLength="1"
      stroke-dasharray="0.08 2"
      stroke-dashoffset="1.08" />

With pathLength="1", a dash of 0.08 is 8% of the path. Animating stroke-dashoffset from 1.08 to -1.08 moves the spark along the entire path. This works regardless of how long or curved the path actually is.

For the infinity loader later in this post, I use pathLength="100" with stroke-dasharray="15 85". Same idea — the sum (15 + 85 = 100) equals the pathLength, guaranteeing exactly one visible dash at all times.

Adding glow

SVG filters add the final polish. A feGaussianBlur creates a soft glow around the spark, and feMerge layers the blur behind the original sharp stroke.

<filter id="glow">
  <feGaussianBlur in="SourceGraphic"
                  stdDeviation="1.2" result="blur" />
  <feMerge>
    <feMergeNode in="blur" />
    <feMergeNode in="SourceGraphic" />
  </feMerge>
</filter>

The blur spreads light around the spark, while the merge ensures the original crisp line stays on top. The stdDeviation controls how far the glow spreads.

Clip-path for animated highlights

A different approach to animation: instead of stroke-dasharray, you can use clip-path: inset() on a regular DOM element to reveal parts of it.

The technique works like this:

This is how Clerk's animated table of contents works. The highlight smoothly slides along the same SVG path, revealing the active section. Because clip-path is GPU-accelerated, it performs well even with complex paths.

clip-path: inset(60px 0 200px 0);
transition: clip-path 500ms ease-out;

Combining two animations

A single stroke-dashoffset animation moves a dash along a path — functional, but mechanical. The trick to making it feel alive is combining two CSS animations with different durations.

@keyframes infinity-move {
  to { stroke-dashoffset: -100; }
}
 
@keyframes infinity-dash {
  0%, 100% { stroke-dasharray: 15 85; }
  50%      { stroke-dasharray: 40 60; }
}

The move animation slides the dash (2s, linear). The dash animation breathes its size from 15% to 40% and back (4s, ease-in-out). The stroke-dasharray always sums to 100 (matching pathLength="100"), so only one dash is ever visible. The different durations create an organic rhythm — the pattern never repeats the same way twice.

The examples

Everything above comes together in these three demos. Each one uses a different combination of the techniques.

Animated wires

Four AI provider icons at the corners, Bezier curves connecting each to the center, animated sparks with glow traveling along the wires. Each spark runs on an independent animation delay. The wires use pathLength="1" with stroke-dasharray, and the glow comes from feGaussianBlur + feMerge filters. The entire thing is a single <svg> element with no JavaScript animation libraries.

Infinity loader

A single dash flowing along an infinity symbol. Two CSS animations combined — one for movement, one for breathing the dash size — create a fluid, organic feel.

Two animations combined: the dash moves along the path AND breathes in size. The different durations (2s vs 4s) create an organic rhythm.

Animated table of contents

A dynamic SVG path generated from a list of items with different indentation levels. Bezier curves connect the transitions between levels. The active section highlight uses clip-path: inset() animated with CSS transitions, and a small dot tracks the end of the selection. Click any item to move the highlight.

This post was heavily inspired by fuma-nama's article on how Clerk built their animated table of contents. The infinity loader technique comes from Fluid Functionalism's button component.