Components

SVG Draw

Wrap any inline SVG and it draws itself in, line by line, then fills.

Wrap any inline SVG, an icon, a logo or a whole illustration, and it draws itself in. Every path, line, circle, rect and polygon traces its outline one after another, then its fill fades in behind the line. Outline icons draw in their own stroke and keep it; filled shapes are traced in a thin line that fades away as they fill. It plays when the SVG scrolls into view, or on mount. GSAP DrawSVG and ScrollTrigger.

New tab

Installation

pnpm dlx shadcn@latest add @tween-ui/svg-draw

Using an AI editor? Set up MCP once, then just ask for it in plain words.

Usage

components/hero-mark.tsx
components/hero-mark.tsx
import SvgDraw from '@/components/tweenui/svg-draw';
 
export default function HeroMark() {
  return (
    <SvgDraw className="text-teal-700">
      <svg
        viewBox="0 0 24 24"
        className="size-24"
        fill="none"
        stroke="currentColor"
        strokeWidth="1.5"
      >
        <path d="M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.6 6.6 19.5l1.2-6-4.5-4.2 6.1-.7z" />
      </svg>
    </SvgDraw>
  );
}

It works with whatever SVG you put inside: an icon from a library, a logo pasted from a design tool, or a whole illustration. Tune the pace with duration, stagger and ease, and change the key to play it again.

Props

PropTypeDefaultDescription
durationnumber1.4Seconds each shape takes to draw.
delaynumber0Seconds before the first shape starts.
staggernumber0.12Seconds between one shape starting and the next.
easestring'power2.inOut'Any GSAP ease.
widthnumber1.5Trace width in screen px for fill-only shapes.
trigger'view' | 'mount''view'Play on scrolling into view, or straight away.
startstring'top 85%'ScrollTrigger start, with trigger="view".
onComplete() => void—Called once every shape is drawn and filled.

The trace line takes the wrapper's text color, so set it with a text-* class. It renders a <div> around your SVG and extends native <div> attributes (className, style, …).

Behavior

  • Any shape, any SVG. It finds every path, line, polyline, polygon, circle, ellipse and rect, however deeply nested in groups or across several SVGs. Shapes inside defs, clipPath, mask, pattern, marker and symbol are skipped, because they are never painted where they sit.
  • Strokes stay strokes, fills stay fills. A stroked shape draws in its own stroke color and width and keeps them. A fill-only shape draws in a thin line in the text color that fades out as its fill fades in. Every fill ends in its own color and opacity, gradients included.
  • The same line on any scale. The trace uses a non-scaling stroke, so width looks the same on a 24-unit icon and on a 1000-unit logo.
  • Measured by DrawSVG. Path lengths, pathLength attributes and line caps are all handled by GSAP's DrawSVG plugin.
  • Cleans up after itself. The timeline runs inside useGSAP, so unmounting or changing a prop reverts every shape to how it was written.
  • No flash. Where scripts run, the SVG is hidden until its first frame is set up, before paint. Without JavaScript it simply shows.
  • Only inline SVG. An <img src="logo.svg"> or a CSS background cannot be traced: paste the markup in, or import it as a component (SVGR). Shapes drawn through <use> and <text> are left as they are.

Accessibility

The drawing is decoration on top of your SVG: its markup, titles and labels are untouched, so give the SVG an aria-label or aria-hidden as you normally would. Under prefers-reduced-motion: reduce nothing animates and the SVG shows as written straight away.