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
<defs>- a container for reusable definitions. Nothing inside<defs>renders directly; it's a "library" of things that other elements reference. Think of it as a hidden storage area.<linearGradient>(or<radialGradient>,<pattern>) - the paint server definition itself, placed inside<defs>and given anid. Elements reference it viafill="url(#id)".
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 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
offset- position along the gradient vector (0 to 1, or 0% to 100%)stop-color- the colour at that positionstop-opacity- opacity at that position (defaults to 1)
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:
x1="0.3" x2="0.7"- the gradient only spans the middle 40% of the element.spreadMethodcontrols what happens outside that range.x1="-0.5" x2="1.5"- the gradient extends beyond the element; only the 0–1 portion is visible (like looking through a window at a larger gradient).x1="0" x2="0.5"- the gradient covers just the left half; the right half is filled according tospreadMethod.
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.
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) - the end colours extend to fill remaining spacereflect- the gradient reverses direction and repeats (mirror effect)repeat- the gradient restarts from the beginning
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:
cx, cy- centre of the end circle (where the last stop sits at the outer edge)r- radius of that outer circlefx, fy- the focal point (where the first stop sits / the "light source")fr(SVG 2) - focal radius, creating ring-shaped gradients instead of point sources
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:
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)"/>
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.
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).
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>
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"/>
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
x, y- origin of the first tilewidth, height- size of each tileviewBox- internal coordinate system of the tile content
patternUnits
Same choice as gradientUnits - controls how the tile's x, y, width, and height are interpreted:
objectBoundingBox(default) - tile dimensions are fractions (0–1) of the target element's bounding boxuserSpaceOnUse- tile dimensions are in absolute user coordinates
patternContentUnits
A separate control for the coordinate system inside the tile - what coordinate system the shapes within the pattern use:
userSpaceOnUse(default) - content coordinates match the referencing element's spaceobjectBoundingBox- content coordinates are 0–1 relative to the target element
patternTransform
Rotates, scales, or skews the entire tiled pattern - same syntax as gradientTransform.
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 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
- Grid backgrounds - a pattern of thin lines creates graph-paper grids
- Hatching fills - rotated stripe patterns for technical/architectural diagrams
- Textured surfaces - noise-like patterns for organic textures
- Data visualisation - patterns as accessible alternatives to colour-only encoding
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?
Question 2
How do you make a linear gradient go from top to bottom?
x1="0" y1="0" x2="1" y2="0" - horizontal axis covers verticalx1="0" y1="0" x2="0" y2="1" - the vector points straight downgradientTransform="vertical"direction="vertical" on the linearGradient elementQuestion 3
What's the difference between patternUnits and patternContentUnits?
Hands-On Exercise
Build these from scratch to cement the concepts:
- 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). - 3D sphere: Create a
<radialGradient>with an offset focal point (fx/fydifferent fromcx/cy) to simulate a 3D sphere. Use white → colour → dark stops. - 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. - 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>