← Back to Index

Lesson 5

Gradients, Patterns & Paint Servers

Previous lessons covered flat colour fills. Time to unlock the real power of SVG's painting model: paint servers. These let you fill and stroke with gradients and repeating patterns - defined once, referenced anywhere, and transformed independently of the shapes they paint.

1. What Are Paint Servers?

SVG's fill and stroke properties don't just accept colour values - they accept a url(#id) reference to a paint server. A paint server is any element defined in <defs> that produces colour data: gradients and patterns.

The term "server" is SVG spec language - the element "serves" paint to whichever elements reference it. You define it once, and any number of shapes can use it as their fill or stroke.

The two key elements you'll see in every example

Here's the pattern you'll see throughout this lesson:

<svg viewBox="0 0 200 100">
  <defs>
    <linearGradient id="myGrad">
      <stop offset="0%" stop-color="navy"/>
      <stop offset="100%" stop-color="gold"/>
    </linearGradient>
  </defs>

  <!-- Any element can reference the gradient -->
  <rect fill="url(#myGrad)" x="10" y="10" width="80" height="80"/>
  <circle fill="url(#myGrad)" cx="150" cy="50" r="40"/>
</svg>

Both shapes reference the same gradient (#myGrad). Each gets its own instance scaled to its bounding box (the default objectBoundingBox behaviour).

Paint servers: define once, reference everywhere

Paint servers are defined once in <defs> and referenced by any number of elements via fill="url(#id)" or stroke="url(#id)". They're the SVG equivalent of CSS gradient backgrounds - but more powerful because they can be referenced by multiple elements and transformed independently.

2. Linear Gradients

<linearGradient> creates a colour transition along a straight line - the gradient vector. You define the vector with x1, y1 (start point) and x2, y2 (end point), then place <stop> elements along it to define the colours.

Stop elements

Default orientation

The default gradient vector is horizontal left-to-right: x1="0" y1="0" x2="1" y2="0". Change the vector to change direction.

Coordinate values for the gradient vector

With objectBoundingBox (the default), 0 means the element's left/top edge and 1 means the right/bottom edge. But you're not limited to 0–1:

With userSpaceOnUse, the values are absolute coordinates in the user coordinate system - any number is valid (200, 500, -100, etc.).

Values between 0 and 1 are the normal case in objectBoundingBox mode, but going outside that range is legal and useful for compressing or offsetting the gradient within the element.

Horizontal (default) Vertical (x2=0, y2=1) Diagonal (x2=1, y2=1)

Three linear gradients: horizontal (default), vertical, and diagonal. The gradient vector direction determines the transition angle.

<defs>
  <!-- Horizontal: left to right (default) -->
  <linearGradient id="horiz">
    <stop offset="0%" stop-color="#e06c75"/>
    <stop offset="100%" stop-color="#61afef"/>
  </linearGradient>

  <!-- Vertical: top to bottom -->
  <linearGradient id="vert" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#98c379"/>
    <stop offset="100%" stop-color="#c678dd"/>
  </linearGradient>

  <!-- Diagonal: top-left to bottom-right -->
  <linearGradient id="diag" x1="0" y1="0" x2="1" y2="1">
    <stop offset="0%" stop-color="#e5c07b"/>
    <stop offset="50%" stop-color="#e06c75"/>
    <stop offset="100%" stop-color="#56b6c2"/>
  </linearGradient>
</defs>

spreadMethod

When the gradient vector doesn't cover the entire shape (you've offset it or shrunk it), spreadMethod controls what happens beyond the vector's endpoints:

pad (default) reflect repeat

The gradient vector only covers the middle 40% of each rect. spreadMethod controls what happens outside it.

3. Radial Gradients

<radialGradient> creates a colour transition radiating outward from a point. Instead of a line, you define a circle:

Same <stop> mechanism as linear gradients - offset, stop-color, stop-opacity all work identically.

The sphere illusion

Offsetting the focal point from the centre creates a convincing 3D sphere effect - the "highlight" appears where the focal point sits:

Basic radial Offset focal point (3D sphere) fr="0.2" (ring gradient, SVG 2)

Radial gradients: basic centred, offset focal point creating a sphere illusion, and a focal radius creating a ring effect.

<defs>
  <!-- 3D sphere: focal point offset to upper-left -->
  <radialGradient id="sphere" cx="0.5" cy="0.5" r="0.5" fx="0.35" fy="0.3">
    <stop offset="0%" stop-color="#fff"/>
    <stop offset="30%" stop-color="#61afef"/>
    <stop offset="100%" stop-color="#0d1f33"/>
  </radialGradient>
</defs>
<circle cx="100" cy="100" r="80" fill="url(#sphere)"/>
Ad

4. gradientUnits - The Crucial Choice

This is where most confusion with SVG gradients lives. The gradientUnits attribute determines what coordinate system the gradient vector (x1, y1, x2, y2 or cx, cy, r) is expressed in:

objectBoundingBox (default)

Coordinates are 0–1, relative to the target element's bounding box. A gradient defined as x1="0" y1="0" x2="1" y2="0" goes from the element's left edge to its right edge - regardless of the element's actual size or position.

userSpaceOnUse

Coordinates are in the current user coordinate system (absolute positions). The gradient is fixed in space, and shapes are like windows looking onto it.

Two mental models for gradient coordinates

objectBoundingBox = "the gradient scales with the shape" (usually what you want for independent elements). userSpaceOnUse = "the gradient is fixed in space and shapes are windows into it" (useful for tiling multiple shapes with one continuous gradient across all of them).

objectBoundingBox (default) Each shape gets its own full gradient userSpaceOnUse All shapes share one continuous gradient

Left: objectBoundingBox - each element gets the full gradient independently. Right: userSpaceOnUse - shapes are windows into a single gradient positioned in absolute coordinates.

<!-- objectBoundingBox: coordinates are 0-1, relative to each element -->
<linearGradient id="obb" gradientUnits="objectBoundingBox"
                x1="0" y1="0" x2="1" y2="0">
  <stop offset="0%" stop-color="red"/>
  <stop offset="100%" stop-color="blue"/>
</linearGradient>

<!-- userSpaceOnUse: coordinates are absolute pixel positions -->
<linearGradient id="usu" gradientUnits="userSpaceOnUse"
                x1="0" y1="0" x2="400" y2="0">
  <stop offset="0%" stop-color="red"/>
  <stop offset="100%" stop-color="blue"/>
</linearGradient>
objectBoundingBox fails on zero-dimension elements

A horizontal <line> has zero height in its bounding box, so a vertical gradient in objectBoundingBox mode produces nothing - you're dividing by zero. Similarly, a vertical line has zero width. Use gradientUnits="userSpaceOnUse" for strokes on lines, or any element where one bounding-box dimension could be zero.

5. gradientTransform

You can rotate, scale, or skew the gradient independently of the shape it fills. The gradientTransform attribute uses the same transform syntax covered in Lesson 2 - rotate(), scale(), translate(), skewX(), matrix().

<linearGradient id="rotated" gradientTransform="rotate(45)">
  <stop offset="0%" stop-color="navy"/>
  <stop offset="100%" stop-color="gold"/>
</linearGradient>

<!-- The gradient is rotated 45 degrees, but the rect stays upright -->
<rect fill="url(#rotated)" x="10" y="10" width="180" height="80"/>
No transform rotate(45) rotate(90)

The same navy-to-gold gradient rotated with gradientTransform. The shapes don't move - only the gradient orientation changes.

When using objectBoundingBox units, the rotation centre for rotate(angle) defaults to (0,0) which is the element's top-left corner. Use the three-argument form rotate(45, 0.5, 0.5) to rotate around the element's centre.

6. Patterns

<pattern> defines a small tile of SVG content that repeats to fill or stroke a target shape. Think of it as a mini SVG that tiles infinitely.

Key attributes

patternUnits

Same choice as gradientUnits - controls how the tile's x, y, width, and height are interpreted:

patternContentUnits

A separate control for the coordinate system inside the tile - what coordinate system the shapes within the pattern use:

patternTransform

Rotates, scales, or skews the entire tiled pattern - same syntax as gradientTransform.

Dot pattern Diagonal stripe pattern

A repeating dot pattern (left) and a diagonal stripe pattern using patternTransform="rotate(45)" (right).

<defs>
  <!-- Dot pattern: 20x20 tile with a centred circle -->
  <pattern id="dots" patternUnits="userSpaceOnUse"
           width="20" height="20">
    <circle cx="10" cy="10" r="3" fill="#61afef"/>
  </pattern>

  <!-- Diagonal stripes: rotate the whole pattern 45 degrees -->
  <pattern id="stripes" patternUnits="userSpaceOnUse"
           width="12" height="12" patternTransform="rotate(45)">
    <rect width="6" height="12" fill="#e06c75"/>
  </pattern>
</defs>

<rect fill="url(#dots)" width="200" height="150"/>
<rect fill="url(#stripes)" x="220" width="200" height="150"/>
Patterns are mini SVGs that tile

Patterns can contain any SVG content - shapes, text, groups, even references to other gradients or patterns. The tile defined by width and height repeats infinitely to fill the target element. Think of each tile as its own tiny SVG viewport.

7. Practical Patterns

Paint servers show up everywhere in real-world SVG work:

Common uses

Pattern inheritance with href

Gradients and patterns support href to inherit from another definition. This saves repeating stops or content:

<defs>
  <!-- Base gradient with shared stops -->
  <linearGradient id="base-grad">
    <stop offset="0%" stop-color="navy"/>
    <stop offset="100%" stop-color="gold"/>
  </linearGradient>

  <!-- Inherits stops from base-grad, but rotated -->
  <linearGradient id="rotated-grad" href="#base-grad"
                  gradientTransform="rotate(90 0.5 0.5)"/>

  <!-- Inherits stops, different direction -->
  <linearGradient id="vertical-grad" href="#base-grad"
                  x1="0" y1="0" x2="0" y2="1"/>
</defs>

Layering patterns

You can layer multiple patterns by nesting SVG elements or using multiple shapes stacked with different pattern fills and partial opacity. Patterns themselves can also reference gradients as fills for their internal shapes, creating rich composites:

<defs>
  <linearGradient id="tile-bg">
    <stop offset="0%" stop-color="#2c313a"/>
    <stop offset="100%" stop-color="#3e4451"/>
  </linearGradient>

  <pattern id="fancy" patternUnits="userSpaceOnUse" width="40" height="40">
    <!-- Pattern content can use gradients! -->
    <rect width="40" height="40" fill="url(#tile-bg)"/>
    <circle cx="20" cy="20" r="8" fill="#61afef" opacity="0.5"/>
  </pattern>
</defs>

Quiz: Check Your Understanding

Question 1

What does gradientUnits="objectBoundingBox" mean?

Gradient coordinates are 0–1, relative to the target element's bounding box
Gradient coordinates are in absolute pixel values
The gradient is fixed in the SVG's root coordinate system
The gradient inherits the element's transform

Question 2

How do you make a linear gradient go from top to bottom?

Set x1="0" y1="0" x2="1" y2="0" - horizontal axis covers vertical
Set x1="0" y1="0" x2="0" y2="1" - the vector points straight down
Use gradientTransform="vertical"
Set direction="vertical" on the linearGradient element

Question 3

What's the difference between patternUnits and patternContentUnits?

They're the same thing - just aliases for backwards compatibility
patternUnits controls rotation; patternContentUnits controls scale
patternUnits controls how the tile's position/size (x, y, width, height) is interpreted; patternContentUnits controls the coordinate system for content inside the tile
patternUnits applies to the fill; patternContentUnits applies to the stroke

Hands-On Exercise

Build these from scratch to cement the concepts:

  1. Navy-to-gold linear gradient: Create a <linearGradient> that transitions from navy to gold. Apply it as the fill of a large <rect> (e.g. 400×200).
  2. 3D sphere: Create a <radialGradient> with an offset focal point (fx/fy different from cx/cy) to simulate a 3D sphere. Use white → colour → dark stops.
  3. Repeating dot pattern: Create a <pattern> with a small circle centred in each tile. Apply it as the fill of a rect. Experiment with tile size to control density.
  4. Rotated gradient: Take your navy-to-gold gradient and use gradientTransform="rotate(45, 0.5, 0.5)" to rotate it 45 degrees. Verify the shape itself doesn't rotate.
<svg width="600" height="500" viewBox="0 0 600 500" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- 1. Navy to gold linear gradient -->
    <linearGradient id="ex-navy-gold">
      <stop offset="0%" stop-color="navy"/>
      <stop offset="100%" stop-color="gold"/>
    </linearGradient>

    <!-- 2. 3D sphere radial gradient -->
    <radialGradient id="ex-sphere" cx="0.5" cy="0.5" r="0.5" fx="0.35" fy="0.3">
      <stop offset="0%" stop-color="#fff"/>
      <stop offset="35%" stop-color="#4a90d9"/>
      <stop offset="100%" stop-color="#0a1628"/>
    </radialGradient>

    <!-- 3. Dot pattern -->
    <pattern id="ex-dots" patternUnits="userSpaceOnUse" width="24" height="24">
      <circle cx="12" cy="12" r="4" fill="coral"/>
    </pattern>

    <!-- 4. Rotated gradient (inherits stops from #ex-navy-gold) -->
    <linearGradient id="ex-rotated" href="#ex-navy-gold"
                    gradientTransform="rotate(45, 0.5, 0.5)"/>
  </defs>

  <!-- 1. Linear gradient rect -->
  <rect x="20" y="20" width="260" height="120" rx="6" fill="url(#ex-navy-gold)"/>

  <!-- 2. 3D sphere -->
  <circle cx="450" cy="80" r="70" fill="url(#ex-sphere)"/>

  <!-- 3. Dot pattern -->
  <rect x="20" y="180" width="260" height="120" rx="6"
        fill="url(#ex-dots)" stroke="#888" stroke-width="1"/>

  <!-- 4. Rotated gradient -->
  <rect x="320" y="180" width="260" height="120" rx="6"
        fill="url(#ex-rotated)"/>
</svg>