Skip to main content

    Lesson 39 • Advanced Track

    SVG Mastery: Paths, Gradients, Animation

    By the end of this lesson you'll be able to draw crisp, resolution-independent graphics in pure markup, style and theme them with CSS custom properties, animate a line so it appears to draw itself, build a reusable icon sprite, and make every graphic accessible to screen readers.

    What You'll Learn

    Write inline SVG and read its viewBox coordinate system
    Draw with rect, circle and path, then colour them with fill and stroke
    Style SVG from CSS and theme it with custom properties
    Animate a line with stroke-dasharray so it draws itself
    Reuse one shape many times with <symbol> and <use>
    Make graphics accessible with <title>, role and aria-label
    Before this lesson: you should be comfortable writing HTML elements and CSS rules, and it helps to have met CSS custom properties and keyframe animations. If var() or @keyframes feel new, skim CSS Custom Properties first — this lesson reuses both.

    💡 Think of It Like This

    A PNG is a photograph; an SVG is a recipe. A photo of a cake is a fixed grid of coloured dots — blow it up on a billboard and you see the dots. A recipe says "a 20cm round tin, two layers" — hand it to a baker and they can make the cake at any size, perfectly, every time.

    SVG stores your graphic as a recipe in text: "draw a circle of radius 50 at (60, 60), fill it green." The browser is the baker. Because it's a recipe, not a snapshot, it stays sharp at any zoom, every ingredient is a real DOM element you can style and animate, and you can change one line to re-colour the whole thing.

    1. Inline SVG & the viewBox Coordinate System

    Inline SVG means writing the <svg> tag straight into your HTML instead of pointing an <img> at a file. The payoff is that every shape inside becomes a real DOM element — you can target it with CSS, hook up events, and inspect it in DevTools.

    The single most important attribute is viewBox="minX minY width height". It sets up the SVG's own little coordinate grid: viewBox="0 0 100 100" means "inside here, the world is 100 units wide and tall." The width/height (or CSS) decide how big that grid is drawn on screen — so a 100-unit grid can be painted at 50px or 500px and the coordinates inside never change. Note (0, 0) is the top-left, and y grows downward.

    Run this. The two squares are drawn with identical coordinates but different on-screen sizes — proof the viewBox is independent of pixels.

    Worked example: one coordinate grid, two sizes

    The viewBox decides the units; width/height decide the pixels

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>viewBox basics</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; }
        svg { border: 1px dashed #94a3b8; }   /* show each SVG's box */
      </style>
    </head>
    <body>
      <h2>Same drawing, two sizes</h2>
    
      <!-- viewBox="0 0 100 100": the inside world is 100 x 100 units.
           width/height = 80px: that world is PAINTED at 80 pixels. -->
      <svg viewBox="0 0 100 100" width="80" height="80">
        <rect x
    ...

    2. Shapes, fill & stroke

    A handful of elements cover most graphics: <rect> (x, y, width, height, plus rx for rounded corners), <circle> (cx, cy centre and r radius), and the do-anything <path>, whose d attribute is a mini drawing language: M move to, L line to, Q/C curves, Z close.

    Every shape has two paint properties: fill (the inside) and stroke (the outline, sized with stroke-width). Set fill="none" for an outline-only shape.

    ElementKey attributesUse for
    <rect>x, y, width, height, rxBoxes, rounded cards
    <circle>cx, cy, rDots, avatars, badges
    <path>d (M, L, Q, C, Z)Anything — icons, curves

    Worked example: build a checkmark badge icon

    rect, circle and path combined, coloured with fill and stroke

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Shapes, fill & stroke</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; }
      </style>
    </head>
    <body>
      <!-- role="img" + <title> make this icon meaningful to screen readers (section 6). -->
      <svg viewBox="0 0 64 64" width="160" height="160" role="img" aria-labelledby="ok-title">
        <title id="ok-title">Success: task complete</title>
    
        <!-- circle: cx/cy is the CENTRE, r
    ...

    3. Styling SVG with CSS & Custom Properties

    Because each shape is a DOM element, the same CSS you already know works on it — just remember the properties are fill, stroke and stroke-width rather than background and border. A CSS rule beats a fill="..." attribute, so you can leave a sensible colour in the markup and override it for hover or dark mode.

    The killer combo is custom properties: store the colour in a --var and read it with var() inside fill. Now one variable re-themes the icon, and stroke="currentColor" makes an icon inherit the surrounding text colour automatically.

    Worked example: theme an icon with a custom property

    fill: var(--icon) and stroke: currentColor driven entirely by CSS

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Styling SVG with CSS</title>
      <style>
        :root { --icon: #3b82f6; }   /* one token themes every icon below */
    
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; color:#e5e7eb; }
        .row { display:flex; gap:24px; align-items:center; }
    
        /* fill reads the variable; change --icon once and all hearts re-colour. */
        .heart { fill: var(--icon); transition: fill .2s, transform .2s; cursor:
    ...

    4. Animating SVG with CSS (the draw effect)

    The famous "line draws itself" effect is a trick with two stroke properties. stroke-dasharray turns a solid line into dashes; make the single dash as long as the whole path and you get one big dash. stroke-dashoffset then slides that dash along the line — push it fully off the end and the line looks empty. Animate the offset back to 0 and the line appears to draw itself.

    It only affects the stroke, so the shape needs fill: none and a stroke. For the exact length use path.getTotalLength() in JS; here a value safely larger than the path works fine.

    SMIL note: SVG also has its own animation tags — <animate> and <animateTransform>, called SMIL. Prefer CSS (shown here) for almost everything: it's better supported, easier to control from JavaScript, and lives with the rest of your styles. Reach for SMIL only for the rare attribute CSS can't animate.

    Worked example: a self-drawing signature line

    stroke-dasharray + stroke-dashoffset animated to 0

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>SVG draw animation</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; }
    
        /* 1) dasharray makes one dash 600 units long (longer than the path).
           2) dashoffset 600 pushes that dash completely off-screen (line empty).
           3) the animation slides the offset back to 0 -> the line "draws". */
        .draw {
          fill: none;                 /* draw effect works on the ST
    ...

    5. Reuse with <symbol> & <use>

    Copy-pasting the same icon markup ten times is wasteful and a nightmare to update. Instead, define each icon once inside a <symbol id="..."> (give the symbol its own viewBox), then stamp it anywhere with <use href="#id" />. This is an "SVG sprite" — one definition, many instances.

    Each <use> can be a different size and colour, and because the shapes use currentColor, the icon takes the surrounding text colour. Fix the definition once and every copy updates.

    Worked example: an icon sprite with <symbol> and <use>

    Define icons once, reuse them at any size and colour

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>SVG sprites</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; color:#e5e7eb; }
        .bar { display:flex; gap:24px; align-items:center; }
        /* size and colour each instance independently; currentColor reads color. */
        .icon { width:40px; height:40px; }
        .blue  { color:#3b82f6; }
        .green { color:#22c55e; }
        .big   { width:64px; height:64px; }
      </style>
    </head>
    <
    ...

    6. Accessibility — Make Graphics Announceable

    A screen reader can't "see" your shapes, so you must describe them. For a meaningful graphic (a logo, an informative chart), add role="img" and an accessible name — the simplest is aria-label="Company logo" on the <svg>. A <title> as the first child also names it and shows as a tooltip; pair it with aria-labelledby for the most reliable support.

    For a decorative graphic that adds nothing for a non-sighted user, do the opposite: aria-hidden="true" so the screen reader skips it entirely. The worst option is leaving a meaningful icon unlabelled and silent.

    Worked example: meaningful vs decorative

    role + aria-label vs <title> vs aria-hidden

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Accessible SVG</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; color:#e5e7eb; }
        .row { display:flex; gap:32px; align-items:center; }
        svg { width:56px; height:56px; }
      </style>
    </head>
    <body>
      <div class="row">
        <!-- MEANINGFUL via aria-label: a reader announces "Download, image". -->
        <svg viewBox="0 0 24 24" role="img" aria-label="Download"
             fill=
    ...

    🎯 Your Turn #1 — Draw and colour a shape

    Finish this inline SVG so it shows a yellow circle with a smiling mouth. Fill in the blanks marked ___, then run it and check the expected result in the comments.

    Your Turn #1: a smiley with circle, fill and a stroked path

    Set the viewBox, fill the face, stroke the smile

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Your Turn 1</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; }
      </style>
    </head>
    <body>
      <!-- 🎯 YOUR TURN — fill in the blanks marked ___ -->
    
      <!-- 1) Give the SVG a coordinate grid 100 units wide and tall. -->
      <svg viewBox="___" width="200" height="200" role="img" aria-label="Smiley face">
        <!-- 👉 viewBox="0 0 100 100" -->
    
        <!-- 2) Draw the face: a circle c
    ...

    🎯 Your Turn #2 — Theme with CSS and draw the line

    Two jobs: colour the icon from a custom property instead of the markup, and make the underline draw itself. Add the var(), the dash properties, and the keyframe target, then verify against the comments.

    Your Turn #2: var() fill + a self-drawing underline

    fill: var(--brand) and a stroke-dashoffset animation to 0

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Your Turn 2</title>
      <style>
        /* 🎯 YOUR TURN — fill in the blanks marked ___ */
    
        :root { --brand: #8b5cf6; }   /* purple brand token */
    
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; color:#e5e7eb; }
    
        /* 1) Colour the star from the variable, NOT a hard-coded hex. */
        .star { fill: ___; }                 /* 👉 var(--brand) */
    
        /* 2) Make this line draw itself. Set BOTH
    ...

    🧩 Mini-Challenge — An accessible icon sprite from scratch

    Support is faded now — only an outline is given. Build a tiny icon sprite and use it twice. Lean on the worked examples in sections 5 and 6 if you get stuck.

    Mini-Challenge: symbol + use, themed and accessible

    Define one icon, stamp it twice at different colours, label it

    Try it Yourself »
    Code Preview
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Mini-Challenge</title>
      <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background:#0f172a; color:#e5e7eb; }
    
        /* 🧩 MINI-CHALLENGE: a reusable, themeable, accessible icon
           1. In a hidden <svg> (width=0 height=0, aria-hidden="true"), define a
              <symbol id="ic-check" viewBox="0 0 24 24"> containing a tick <path>
              that uses fill="none" stroke="currentColor".
           2. Stamp it
    ...

    ⚠️ Common Errors (and the fix)

    • Missing viewBox. Without viewBox the browser has no coordinate grid to scale, so the graphic clips or refuses to resize when you change width/height. Fix: add viewBox="0 0 W H" matching the coordinates you drew in (e.g. 0 0 24 24).
    • No accessible name. A meaningful icon with no label is invisible to screen readers — they announce nothing, or read raw markup. Fix: add role="img" plus aria-label (or a <title>); for decoration use aria-hidden="true".
    • fill attribute won't change on hover. Setting color: red on an SVG does nothing — shapes use fill/stroke, not color directly. Fix: style fill in CSS, or set fill="currentColor" so color drives it.
    • Draw effect does nothing. Animating stroke-dashoffset on a shape with a fill and no stroke shows no change. Fix: set fill: none, give it a stroke, and make stroke-dasharray ≥ the path length.
    • Using SVG for a photo. SVG describes shapes, not pixels — there's nothing to "describe" in a photograph, so it bloats or can't be done. Fix: use a raster format (<img> with PNG/JPG/WebP) for photos; keep SVG for icons, logos and line art.

    📋 Quick Reference

    GoalUse this
    Set the coordinate grid<svg viewBox="0 0 24 24">
    Rectangle / rounded box<rect x y width height rx />
    Circle<circle cx cy r />
    Any shape / line<path d="M.. L.. Q.. Z" />
    Outline onlyfill="none" stroke="..." stroke-width="2"
    Inherit text colourstroke="currentColor" / fill="currentColor"
    Theme from CSSfill: var(--brand);
    Draw a line onstroke-dasharray + dashoffset → 0
    Reuse an icon<symbol id> … <use href="#id"/>
    Accessibilityrole="img" aria-label="…" / aria-hidden="true"

    ❓ Frequently Asked Questions

    What is the difference between SVG and a PNG or JPG?

    PNG and JPG are raster formats: they store a fixed grid of coloured pixels, so when you scale them up the browser has to invent pixels and the image goes blurry or blocky. SVG is a vector format: it stores instructions like 'draw a circle of radius 50 here' as text, and the browser redraws those instructions at whatever size you ask for, so it stays razor-sharp at any zoom or screen density. Use SVG for icons, logos, charts, and line art; use PNG or JPG for photographs, where there is no shape to describe — only millions of individual pixels.

    Why does my SVG ignore width and height, or look the wrong size?

    Almost always a missing or wrong viewBox. The viewBox defines the SVG's internal coordinate grid (its own little world of units); width and height define how big that grid is drawn on the page. Without a viewBox the browser can't map your 0-24 coordinates onto a 96px box, so the graphic gets clipped or sized strangely. Add viewBox="0 0 24 24" (matching the coordinates you drew in) and then width/height — or CSS — can scale it freely.

    Should I set colours with the fill attribute or with CSS?

    Either works, but CSS wins when there's a conflict, and CSS is what you want for anything that changes — hover, dark mode, theming. A presentation attribute like fill="red" on the element is the weakest source, so a CSS rule (even fill: blue from a class) overrides it. The exception is an inline style attribute, which beats both. The idiomatic pattern for icons is to set fill="none" stroke="currentColor" in the markup and let the parent's CSS color drive the colour.

    How does the stroke-dasharray drawing animation actually work?

    You turn the line into a dashed line whose single dash is as long as the whole path, then push that dash off the end with stroke-dashoffset so nothing shows. Animating the offset back to 0 slides the dash into view, which looks like the line drawing itself. Set stroke-dasharray and stroke-dashoffset to the path's total length (use getTotalLength() in JS for the exact number, or a value big enough to cover it), then animate stroke-dashoffset to 0. It only works on the stroke, so the shape needs a stroke and usually fill: none.

    Should I use CSS animation or SMIL (<animate>) for SVG?

    Prefer CSS (and the Web Animations API in JS) for almost everything. SMIL is SVG's built-in animation syntax — tags like <animate> and <animateTransform> embedded right in the markup — and it can animate a few attributes CSS historically couldn't, but it's quirky, harder to control from JavaScript, and was once deprecation-flagged in Chrome. CSS keyframes are better supported, more familiar, and integrate with the rest of your stylesheet, so reach for SMIL only for a specific attribute animation CSS can't express.

    How do I make an SVG accessible to screen readers?

    If the SVG conveys meaning (a logo, an informative chart), give it role="img" and an accessible name — either aria-label="Company logo" on the <svg>, or a <title> element as the first child (the <title> also shows as a tooltip). If the SVG is purely decorative, hide it instead with aria-hidden="true" so screen readers skip it. Don't rely on a <title> alone without role="img", because support for announcing it is inconsistent across screen readers.

    🎉 Lesson Complete

    You can now draw, style, animate and label vector graphics in pure markup. The essentials:

    • Inline SVG shapes are DOM elements — styleable, scriptable, inspectable
    • viewBox sets the coordinate grid; width/height set the pixels — that's what makes SVG resolution-independent
    • ✅ Draw with rect/circle/path; paint with fill and stroke
    • ✅ Style from CSS and theme with var() / currentColor
    • stroke-dasharray + stroke-dashoffset → 0 makes a line draw itself (prefer CSS over SMIL)
    • <symbol> + <use> = define once, reuse everywhere
    • ✅ Add role="img" + aria-label (or aria-hidden for decoration)

    Next up: Responsive Navigation, where you'll build a menu that adapts from desktop bar to mobile drawer.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service