DEV Community

Cover image for ✨ How to Create SVGs in Figma and Animate Them Using Motion 🚀
Sushil
Sushil

Posted on

✨ How to Create SVGs in Figma and Animate Them Using Motion 🚀

SVGs are one of the most powerful tools in modern UI development. They’re scalable, lightweight, customizable, and—best of all—easy to animate when paired with Motion.

In this guide, I’ll walk you through how to:

  • Design SVGs in Figma
  • Export them cleanly
  • Convert them into React components
  • Animate them with Motion (Framer Motion)

Let’s jump right in.

Open Figma and create a new frame.

Creating SVG lines is simple — select the Pen tool, click to set your starting point, and then click again where you want the line to end. Figma will automatically create a clean vector path that you can later export as an SVG



To create curves, we’ll use the Bend tool.



Select your SVG line, click on the Bend tool, and then click on the line again. You’ll see two Dots appear, just drag those dots to shape the curve however you like. That’s it! You now have a clean, smooth, curved line.

Once your line looks good, exporting it as an SVG is easy. Just select the line, right-click, and choose Copy as SVG.



I’ve already created a demo component with some icons and a few pre-built SVG lines animating in the background. Now, let’s take a look at how we can animate the left SVG line using Motion.



"use client";

import MotionDiv from "@/components/animation/motion-div";
import { cn } from "@/lib/utils";
import React from "react";
import { motion } from "motion/react";
import { DotPattern } from "@/components/ui/dot-pattern";
import Container from "@/components/container";
import ThemeToggle from "@/components/theme-toggle";
import Scales from "@/components/scales";

const Demo = () => {
  return (
    <Container className="relative flex h-screen max-w-[524px] flex-col items-center justify-center bg-white dark:bg-black">
      <div className="mb-4">
        <ThemeToggle />
      </div>
      <Scales />
      <div className="relative overflow-hidden border-y">
        <Heading
          heading="Multi-Model Orchestration"
          subHeading="Use different AI models together in a single flow to optimize accuracy, creativity, and performance across tasks like generation, enhancement, and analysis."
        />
        <div className="flex scale-90 flex-col items-center justify-center sm:scale-105 md:scale-110 lg:scale-125">
          <MotionDiv className="relative flex h-72 w-[484px] scale-80 items-center justify-center bg-white p-2 px-4 sm:px-0 dark:bg-black">
            <DotPattern
              className={cn(
                "[mask-image:radial-gradient(150px_circle_at_center,white,transparent)]",
                "text-neutral-400 dark:text-neutral-700",
              )}
            />

            <IconCard className="left-8.5" icon={<GeminiIcon />} />
            <IconCard className="left-25.5" icon={<OpenAiIcon />} />
            <IconCard className="left-42.5" icon={<DeepSeekIcon />} />
            <IconCard className="right-42.5" icon={<ClaudeIcon />} />
            <IconCard className="right-25.5" icon={<GrokIcon />} />
            <IconCard className="right-8.5" icon={<QwenAiIcon />} />

            <Line1 />
            <Line2 />
            <Line3 />
            <Line4 />
            <Line5 />
            <Line6 />
            <MotionDiv
              initial={{}}
              className="bg-card absolute right-1/2 bottom-6 flex size-14.5 translate-x-1/2 items-center justify-center rounded-full border p-1"
            >
              <div className="flex size-full items-center justify-center rounded-full p-2.5 text-center shadow-2xl">
                SM
              </div>
            </MotionDiv>
          </MotionDiv>
        </div>
      </div>
    </Container>
  );
};

export default Demo;

type Heading = {
  heading: string;
  subHeading: string;
};

const Heading = ({ heading, subHeading }: Heading) => {
  return (
    <div className="mb-2 flex flex-col items-start justify-center gap-0.5 px-6 pt-8 sm:mb-8 lg:pt-4">
      <h2 className="text-card-title text-base font-medium tracking-tight sm:text-lg">
        {heading}
      </h2>
      <p className="text-paragraph mt-1 max-w-lg text-xs leading-snug font-light text-pretty sm:text-sm">
        {subHeading}
      </p>
    </div>
  );
};

type IconCardType = {
  className: string;
  icon: React.ReactNode;
};

const IconCard = ({ className, icon }: IconCardType) => {
  return (
    <MotionDiv
      className={cn(
        `bg-card absolute top-10 z-10 flex w-fit items-center justify-center rounded-full border shadow-2xl inset-shadow-sm`,
        className,
      )}
    >
      <div className="flex size-9.5 items-center justify-center rounded-full p-1.5">
        {icon}
      </div>
    </MotionDiv>
  );
};


const Line2 = () => {
  return (
    <svg
      width="107"
      height="166"
      viewBox="0 0 107 166"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-30"
      stroke="url(#line-2)"
    >
      <path strokeWidth={2} d="M106.5 166C106.5 91.8654 0.5 93.6701 0.5 0" />
      <defs>
        <motion.linearGradient
          id="line-2"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "100%",
            y1: "90%",
            y2: "115%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}
        >
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#10b981" />
          <stop offset="0.66" stopColor="#0891b2" />
          <stop offset="0.1" stopColor="#eeeeee" />
        </motion.linearGradient>
      </defs>
    </svg>
  );
};
const Line3 = () => {
  return (
    <svg
      width="50"
      height="166"
      viewBox="0 0 50 166"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-47"
      stroke="url(#line-3)"
    >
      <path strokeWidth={2} d="M49.5 166C49.5 93.9514 0.5 93.9514 0.5 0" />
      <defs>
        <motion.linearGradient
          id="line-3"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "100%",
            y1: "90%",
            y2: "115%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}
        >
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#10b981" />
          <stop offset="0.66" stopColor="#0891b2" />
          <stop offset="0.1" stopColor="#eeeeee" />
        </motion.linearGradient>
      </defs>
    </svg>
  );
};

const Line4 = () => {
  return (
    <svg
      width="50"
      height="166"
      viewBox="0 0 50 166"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 right-47 rotate-y-180"
      stroke="url(#line-4)"
    >
      <path strokeWidth={2} d="M49.5 166C49.5 93.9514 0.5 93.9514 0.5 0" />
      <defs>
        <motion.linearGradient
          id="line-4"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "100%",
            y1: "90%",
            y2: "115%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}
        >
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#10b981" />
          <stop offset="0.66" stopColor="#0891b2" />
          <stop offset="0.1" stopColor="#eeeeee" />
        </motion.linearGradient>
      </defs>
    </svg>
  );
};
const Line5 = () => {
  return (
    <svg
      width="107"
      height="166"
      viewBox="0 0 107 166"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 right-30 rotate-y-180"
      stroke="url(#line-2)"
    >
      <path strokeWidth={2} d="M106.5 166C106.5 91.8654 0.5 93.6701 0.5 0" />
      <defs>
        <motion.linearGradient
          id="line-2"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "100%",
            y1: "90%",
            y2: "115%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}
        >
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#10b981" />
          <stop offset="0.66" stopColor="#0891b2" />
          <stop offset="0.1" stopColor="#eeeeee" />
        </motion.linearGradient>
      </defs>
    </svg>
  );
};
const Line6 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 right-13 rotate-y-180"
    >
      <path
        strokeWidth={2}
        stroke="url(#line-6)"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      />

      <defs>
        <motion.linearGradient
          id="line-6"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "100%",
            y1: "90%",
            y2: "115%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}
        >
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#10b981" />
          <stop offset="0.66" stopColor="#0891b2" />
          <stop offset="0.1" stopColor="#eeeeee" />
        </motion.linearGradient>
      </defs>
    </svg>
  );
};


Enter fullscreen mode Exit fullscreen mode

Let’s take the SVG we created in Figma and use it here.
First, we’ll turn it into a component called Line1.

Here’s what the component looks like.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      <path
        strokeWidth={2}
        stroke="#eeeeee"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      />
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now, to animate the line, we’ll use Motion with a linear gradient.
To do that, we need to place our gradient inside a <defs> section, which is where SVG stores reusable definitions.

Next, we’ll turn the linear gradient into a Motion element and give it an ID so we can reference it from the SVG stroke.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      <path
        strokeWidth={2}
        stroke="url(#line-1)"  // use the id here
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      />

<defs>
<motion.linearGradient
id="line-1"
>
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#dc2626" />
          <stop offset="0.66" stopColor="#3b82f6" />
          <stop offset="1" stopColor="#eeeeee" />
</motion.linearGradient>
</defs>
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode

A <stop> defines one color point inside a gradient.
A gradient is essentially a list of stops that blend seamlessly into one another.

stopColor is the actual color for that stop.

offset defines where the color appears in the gradient.

It is like a progress percentage:

  • 0 or 0% → start of the gradient
  • 1 or 100% → end of the gradient
  • 0.5 or 50% → middle

Inside the linear gradient, we have four values: x1 and x2 control how the gradient moves along the x-axis, and y1 and y2 control movement along the y-axis.

For this example, let’s start by animating x1 and x2.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      <path
        strokeWidth={2}
        stroke="url(#line-1)"  // use the id here
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      />

<defs>
<motion.linearGradient
id="line-1"
          initial={{
            x1: "0%",
            x2: "10%",
          }}

>
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#dc2626" />
          <stop offset="0.66" stopColor="#3b82f6" />
          <stop offset="1" stopColor="#eeeeee" />
</motion.linearGradient>
</defs>
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode


In the original gradient, x1 = 0% and x2 = 10% define where the red/blue part of the gradient starts.

Now, you can probably guess what we’re going to do
we’ll animate those same values so the gradient moves across the line.
So instead of starting at 0% → 10%, we’ll animate them to 90% → 100%.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      <path
        strokeWidth={2}
        stroke="url(#line-1)"  // use the id here
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      />

<defs>
<motion.linearGradient
id="line-1"
          initial={{
            x1: "0%",
            x2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "110%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}

>
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#dc2626" />
          <stop offset="0.66" stopColor="#3b82f6" />
          <stop offset="1" stopColor="#eeeeee" />
</motion.linearGradient>
</defs>
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode

we’re basically telling the gradient to slide forward along the line, creating that smooth glowing effect.

The gradient starts at its initial position and then moves from 90% to 110%, looping continuously. The linear easing keeps the motion steady, while the repeat delay adds a small pause before the next cycle, making the animation feel more natural and less aggressive.

Let's see it in action



But as you can see, the gradient doesn’t start from the top, it appears somewhere in the middle.

That’s happening because we’re only animating the x-axis values, not the y-axis. The gradient is technically moving, but only horizontally, so the effect doesn’t align with the shape of the curve.

To understand this better, we can use a simple <rect> example. A behaves similarly to a <path>, but because it’s a clean, straight shape, it makes the gradient behavior much easier to see.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      {/* <path
        strokeWidth={2}
        stroke="url(#line-1)"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      /> */}

      <rect
        width="169"
        height="167"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
        fill="url(#line-1)"
      />

<defs>
<motion.linearGradient
id="line-1"
          initial={{
            x1: "0%",
            x2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "110%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}

>
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#dc2626" />
          <stop offset="0.66" stopColor="#3b82f6" />
          <stop offset="1" stopColor="#eeeeee" />
</motion.linearGradient>
</defs>
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode


So now let’s also animate the y-axis values.
By adjusting y1 and y2 along with the x-axis, the gradient will follow the full direction of the line instead of cutting through the middle.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      {/* <path
        strokeWidth={2}
        stroke="url(#line-1)"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      /> */}

      <rect
        width="169"
        height="167"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
        fill="url(#line-1)"
      />

<defs>
<motion.linearGradient
id="line-1"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "110%",
            y1: "90%",
            y2: "110%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}

>
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#dc2626" />
          <stop offset="0.66" stopColor="#3b82f6" />
          <stop offset="1" stopColor="#eeeeee" />
</motion.linearGradient>
</defs>
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode


Now just remove the and switch back to your , and you’ll have a clean, modern linear-gradient animation flowing perfectly across your SVG line.

const Line1 = () => {
  return (
    <svg
      width="169"
      height="167"
      viewBox="0 0 169 167"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="absolute top-19 left-13"
    >
      <path
        strokeWidth={2}
        stroke="url(#line-1)"
        d="M168.499 166.026C168.499 99.7414 5.52495 96.283 0.499313 0.0260773"
      /> 
<defs>
<motion.linearGradient
id="line-1"
          initial={{
            x1: "0%",
            x2: "10%",
            y1: "0%",
            y2: "10%",
          }}
          animate={{
            x1: "90%",
            x2: "110%",
            y1: "90%",
            y2: "110%",
          }}
          transition={{
            duration: 0.8,
            repeat: Infinity,
            repeatDelay: 1,
            repeatType: "loop",
            ease: "linear",
          }}

>
          <stop stopColor="#eeeeee" />
          <stop offset="0.33" stopColor="#dc2626" />
          <stop offset="0.66" stopColor="#3b82f6" />
          <stop offset="1" stopColor="#eeeeee" />
</motion.linearGradient>
</defs>
    </svg>
  );
};
Enter fullscreen mode Exit fullscreen mode

End Result:



And that’s it — we’ve learned how to create an SVG in Figma, export it, and animate it using Motion.

If you found this helpful, follow me on Twitter for more tips, breakdowns, and behind-the-scenes builds.

I’m also launching my template soon, so stay tuned — you won’t want to miss it! 🚀

Top comments (0)