Chapter 10 of 11Part 4 · Finish
SVG line animation: make a path draw itself
Make an SVG path draw itself with stroke-dasharray and stroke-dashoffset, in CSS or GSAP, without confusing screen readers.
9 minBy Zuzo StudioUpdated
In this chapter you will
- explain how a dash as long as the path makes a line appear to draw
- set up a path with pathLength="1" or getTotalLength() and animate it in CSS and GSAP
- hide a decorative SVG from screen readers and keep it visible without JavaScript
Before you startThis builds on Scroll animation with GSAP and CSS.
A line that draws itself is one of the oldest tricks in web motion, and it is still one of the nicest: an underline that sweeps under a key word, an icon that sketches its own outline, a route that traces across a map. SVG line animation looks like it needs a special tool, but it is built from two ordinary stroke properties and one measurement. This chapter explains the trick, shows it in CSS and in GSAP, and covers what screen readers should hear about a decorative line, which is usually nothing. It follows on from scroll animation, and the result goes straight into your hero timeline.
01How an SVG path is drawn
SVG (Scalable Vector Graphics) describes shapes as instructions instead of pixels, so it stays sharp at any size and lives in your HTML like any other element. The shape we care about is <path>. Its d attribute is a list of drawing commands: M moves the pen without drawing, C and Q draw curves, L draws a straight line.
<svg viewBox="0 0 200 20" aria-hidden="true">
<path d="M2 14 C 50 4, 120 4, 198 12"
fill="none" stroke="currentColor"
stroke-width="3" stroke-linecap="round" />
</svg>
A few attributes matter for line animation:
viewBoxsets the SVG's own coordinate system, here 200 units wide and 20 tall. The SVG then scales to whatever size CSS gives it.fill="none"keeps the browser from filling the shape. A line animation is all stroke.stroke="currentColor"paints the line in the text colour of its parent, so it follows your theme.stroke-linecap="round"gives the ends a soft, hand drawn finish.
You rarely write d by hand. Draw the line in Figma or Illustrator, export it as SVG, and copy the <path> into your page. Delete any fill and style attributes the export added, so your CSS is in charge.
02The dash trick behind SVG line animation
Every stroke can be dashed. The stroke-dasharray property sets the pattern: stroke-dasharray: 10 5 draws 10 units of line, then a 5 unit gap, and repeats. The stroke-dashoffset property slides that pattern along the path. MDN's stroke-dasharray reference has worked examples of the pattern syntax.
Now make the dash as long as the whole path, and the gap just as long. One dash covers the entire line, so it looks solid. Set the offset to the full length and the pattern slides along until only the gap is showing: the line disappears. Animate the offset back to 0 and the dash slides into place from the start of the path. To the eye, the line is drawing itself.
| Step | stroke-dasharray | stroke-dashoffset | What you see |
|---|---|---|---|
| Hidden | length length | length | Nothing, only the gap |
| Half way | length length | length / 2 | The first half of the line |
| Drawn | length length | 0 | The whole line |
Drag the slider below to scrub the offset from the full length down to 0, or press Play to animate it. The panel shows the path length and the current stroke-dasharray and stroke-dashoffset values, so you can watch the numbers that make the illusion.
Setting stroke-dasharray to the length of a path and animating stroke-dashoffset from that length to zero makes the line appear to draw itself.
03Measuring the path, or not
The trick needs the length of the path. There are two ways to get it.
getTotalLength()
Every path element has a getTotalLength() method. MDN's getTotalLength reference describes it as returning the browser's computed length of the path in user units, the units of the viewBox.
const path = document.querySelector('.underline path');
const length = path.getTotalLength();
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;
This works everywhere and is the classic approach, but it means JavaScript has to run before the line can be hidden, and you have to measure again if you swap the path.
pathLength="1"
The tidier option is to tell the browser what length to use. MDN's page on the pathLength attribute explains that it lets you declare a total length for the path, and the browser scales its distance calculations, dashes included, to match. Set pathLength="1" and the whole path counts as 1 unit long, whatever its real size:
<path pathLength="1" d="M2 14 C 50 4, 120 4, 198 12" />
Now the numbers are always the same: a dasharray of 1 and an offset going from 1 to 0. No measuring, no script, and the same CSS works for every path on the site.
04Drawing it with CSS
With pathLength="1", the CSS version is a few lines of keyframes from chapter 5:
.underline path {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
.js .underline path {
animation: draw 0.8s cubic-bezier(0.65, 0, 0.35, 1) 0.4s both;
}
@keyframes draw {
from { stroke-dashoffset: 1.01; }
}
The base rule leaves the line fully drawn. Only when a js class is on the page, set by a one line script, does the animation hide it and draw it in. If the script never runs, the reader still gets the underline. Animating stroke-dashoffset does repaint the SVG, so it is not as cheap as transform, but for one short line drawn once the cost is tiny.
05Drawing it with GSAP
In a GSAP project, the line is just another tween. GSAP can animate the strokeDashoffset property directly, which means the draw can sit inside the hero timeline you built in GSAP timelines and be placed with the position parameter like everything else.
gsap.fromTo('.underline path',
{ strokeDashoffset: 1.01 },
{ strokeDashoffset: 0, duration: 0.8, ease: 'power2.inOut' }
);
GSAP also has a dedicated DrawSVG plugin. Its docs explain that it controls stroke-dashoffset and stroke-dasharray for you, so you never handle the length yourself. It can also draw from the middle out or show any segment, such as drawSVG: "20% 80%". For a single underline the plain tween above is enough. Reach for the plugin when you have many paths of different lengths or want segments that grow from both ends.
06Accessibility of decorative SVG
An underline, a squiggle or a sketched arrow next to a heading adds no information. A screen reader should skip it entirely. Mark it with aria-hidden="true" on the <svg> element, and do not give it a <title>. This matches the W3C guidance on decorative images: images that only add visual style should be hidden from assistive technologies so they do not add clutter.
When the drawn line does carry meaning, such as a chart line or an icon that stands alone as a button, it is not decorative. Give the <svg> role="img" and an aria-label that says what it shows, or put a real text label next to it. The accessibility basics chapter of the first course covers labels in more depth.
Two more rules keep line animation friendly:
- Never hide the meaning behind the animation. If the underline marks the important word, the word itself should already be bold or coloured. The line is emphasis, not the message.
- Draw it once. A line that redraws on a loop is moving content, and the next chapter, reduced motion, shows where WCAG draws the line on that.
.mark { position: relative; white-space: nowrap; }
.mark .underline {
position: absolute;
left: 0; bottom: -0.3em;
width: 100%; height: auto;
overflow: visible;
}
.underline path { stroke-dasharray: 1; stroke-dashoffset: 0; }
Check your understanding
3 questions
Key takeaways
- A line draws itself when its dash is as long as the path and stroke-dashoffset animates from that length to 0.
- pathLength="1" makes every path count as 1 unit long, so one set of CSS values works for any path without measuring.
- getTotalLength() measures a path in viewBox units when you need the real length in JavaScript.
- Leave the line drawn in the base CSS and hide it only when the animation is about to run, so it survives a failed script.
- A decorative SVG gets aria-hidden="true"; one that carries meaning gets role="img" and a label.
Sources
- stroke-dasharrayMDN Web Docs, checked
- pathLengthMDN Web Docs, checked
- SVGGeometryElement: getTotalLength() methodMDN Web Docs, checked
- DrawSVGGSAP, checked
- Decorative imagesW3C Web Accessibility Initiative, checked