I present... the animated Valentine's Day Ribbon SVG. I actually created it with the help of the SVG Maker we made last month, but we don't have to use that if we don't want to. This little SVG can be scaled up or down, and placed anywhere you want!
Here's the file to test the SVG on. We'll link to vday2026.svg, and view it at different scales. The page has a black background, but feel free to test with different background colors too.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Vday 2026 test</title>
</head>
<body style="background-color:black">
<img src="vday2026.svg" />
<img src="vday2026.svg" width="50" />
<img src="vday2026.svg" width="500" />
</body>
</html>
<html>
<head>
<title>Vday 2026 test</title>
</head>
<body style="background-color:black">
<img src="vday2026.svg" />
<img src="vday2026.svg" width="50" />
<img src="vday2026.svg" width="500" />
</body>
</html>
And here we begin with the standard SVG markup. We'll create it as a 300 by 300 pixel image.
vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
</svg>
</svg>
We will start off by defining some colors. This is the background that we will call valGradient. It's a linear gradient that goes from red to pink.
vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
</defs>
</svg>
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
</defs>
</svg>
valGradient2 is the exact opposite, going from pink to red.
vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
<linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(250, 200, 200)">
</stop>
<stop offset="100%" stop-color="rgb(200, 0, 0)">
</stop>
</linearGradient>
</defs>
</svg>
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
<linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(250, 200, 200)">
</stop>
<stop offset="100%" stop-color="rgb(200, 0, 0)">
</stop>
</linearGradient>
</defs>
</svg>
Now that we have our colors, let's start drawing the ribbon. We set the outline to a thick orange line so that we can see what we're doing.
vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
<linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(250, 200, 200)">
</stop>
<stop offset="100%" stop-color="rgb(200, 0, 0)">
</stop>
</linearGradient>
</defs>
<path
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
</svg>
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
<linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(250, 200, 200)">
</stop>
<stop offset="100%" stop-color="rgb(200, 0, 0)">
</stop>
</linearGradient>
</defs>
<path
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
</svg>
The path's d attribute starts with an M directive to move to point (150, 50). The Z directive is to return to point of origin, which is (150, 50).
vday2026.svg
<path d="M150 50 Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
This basically means to move to point (125, 30) but curve towards (140, 25).
vday2026.svg
<path d="M150 50
Q140 25 125 30
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Q140 25 125 30
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Let's apply a second curve so that the path should form an "n" shape, like a hill.
vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Q140 25 125 30
Q100 43 100 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
That's what we should have so far. This is the top left part of the "heart". You should see that we have three of these shapes. The leftmost one is the default size, the middle one is scaled down and the rightmost one is enlarged.
Here's the rest of the points that make up the entire left side of the "heart".
vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Coming along nicely. Bear in mind this is not meant to be perfect, but look kind of organic, if that makes sense.
Without belaboring the point, this is the entire first path.
vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
I think it's coming along quite nicely, wouldn't you say?
Here is the next path. It's a lot shorter.
vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
...yep.
This last one is the shortest of all.
vday2026.svg
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />
And that's all the paths. But this is visually a little weird. The shading has no continuity.
We rectify this by using the reverse gradient, valGradient2. for the last two paths.
vday2026.svg
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="2" />
<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="2" />
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="2" />
<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="2" />
Looks good now! Or, at least, less weird.
Now let's just remove the outlines.
vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="0" />
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="0" />
<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="0" />
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="0" />
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="0" />
<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="0" />
So far so good...
Now what we need to do is animate the gradients. For valGradient, the first color, as defined by the attribute stop-color, is red. We use the animate tag to animate the stop-color attribute, making it go from red to pink, and red again. repeatCount is set to indefinite to make it go on forever. The dur attribute is set to 4 seconds, though you can certainly vary this if you want.
vday2026.svg
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
<stop offset="0%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
</stop>
</linearGradient>
For the second color, we do the reverse. It starts off pink, so we make it go red, then pink again.
vday2026.svg
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
<animate attributeName="stop-color" values="rgb(250, 200, 200);rgb(200, 0, 0);rgb(250, 200, 200)" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
<stop offset="0%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
<animate attributeName="stop-color" values="rgb(250, 200, 200);rgb(200, 0, 0);rgb(250, 200, 200)" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
For valGradient2, we repeat, but in reverse!
vday2026.svg
<defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
<animate attributeName="stop-color" values="rgb(250, 200, 200);rgb(200, 0, 0);rgb(250, 200, 200)" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
<linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(250, 200, 200)">
<animate attributeName="stop-color" values="rgb(250, 200, 200);rgb(200, 0, 0);rgb(250, 200, 200)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
</defs>
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(250, 200, 200)">
<animate attributeName="stop-color" values="rgb(250, 200, 200);rgb(200, 0, 0);rgb(250, 200, 200)" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
<linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgb(250, 200, 200)">
<animate attributeName="stop-color" values="rgb(250, 200, 200);rgb(200, 0, 0);rgb(250, 200, 200)" dur="4s" repeatCount="indefinite" />
</stop>
<stop offset="100%" stop-color="rgb(200, 0, 0)">
<animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
</stop>
</linearGradient>
</defs>
The romantic conclusion
This is a very small web tutorial, mostly because I didn't see the point of charting out every single point in the path. Still, I hope this illustrates why I enjoy SVGs so much. Even with just a small subset of all the features available, there's plenty that can be done.Yours pathologically,
T___T
T___T























































