Most people meet gradients in CSS first, so the natural assumption is that SVG works the same way: write a function, get a gradient. It does not. In SVG a gradient is a separate element that you define once, give an id, and then reference from any shape that should be painted with it. The gradient itself draws nothing. It is a paint server, a reusable description of how to fill space, and shapes opt into it by pointing their fill or stroke at its id.
That indirection is the source of both the power and the bugs. One gradient can paint fifty shapes consistently, which no CSS approach matches. And a broken reference, a stripped definition or a colliding id turns those same fifty shapes black without a single error anywhere. Understanding the model is most of the battle, so that is where this starts.
How gradients work in SVG
The minimum working example has three parts: a definition, an id, and a reference.
<svg viewBox="0 0 200 100">
<defs>
<linearGradient id="fade">
<stop offset="0%" stop-color="#7C5CFF"/>
<stop offset="100%" stop-color="#00C2A8"/>
</linearGradient>
</defs>
<rect width="200" height="100" fill="url(#fade)"/>
</svg>
The defs element holds things that should exist without being drawn, the gradient sits inside it with an id, and the rectangle paints itself with fill="url(#fade)". Everything else in this guide is refinement of those three parts.
Worth settling immediately: you cannot write fill="linear-gradient(...)". The CSS gradient functions produce images for HTML backgrounds and SVG paint does not accept them. The two systems live side by side and do not mix, which also means SVG has no conic gradient at all, because conic-gradient() exists only on the CSS side. SVG gives you exactly two gradient elements, linear and radial, and they cover more ground than that sounds like.
Linear gradients
A linearGradient paints colour along a straight line defined by two points, x1 y1 to x2 y2. Every point on the shape gets the colour of the nearest position along that line. The default is x1="0%" y1="0%" x2="100%" y2="0%", a left-to-right sweep, which is why the example above works with no coordinates at all.
The coordinates are how you set direction. Top-to-bottom is x1="0" y1="0" x2="0" y2="1". A diagonal from the top-left corner to the bottom-right is x1="0" y1="0" x2="1" y2="1", and note that on a non-square shape this follows the shape's corners, not a true 45 degrees, for reasons the gradientUnits section makes clear. If you find yourself fighting the coordinates to get a specific angle, stop and use gradientTransform instead, covered below, because rotating the gradient is far easier to reason about than solving for endpoint positions.
Radial gradients
A radialGradient paints colour outward from a point in expanding circles. Three attributes define the outer edge: cx, cy and r, all defaulting to 50%, which gives a glow centred on the shape that reaches its edges. The 100% stop lands on that outer circle and the 0% stop at the centre.
The interesting controls are the focal ones. fx and fy move the point the gradient radiates from without moving the outer circle, which is how you get the off-centre highlight that makes a flat circle read as a lit sphere. fr sets a radius for that focal point, so the first colour holds as a solid disc before the transition begins; it has been safe to use in every major browser for years now. A little of this goes a long way: a highlight nudged to the upper left with fx="0.35" fy="0.35" is the entire technique behind most "3D" badge effects.
Getting the stops right
Stops are the colours. Each stop takes an offset between 0 and 1 or 0% and 100%, a stop-color, and an optional stop-opacity. Keep the offsets in ascending order; the spec makes browsers clamp out-of-order stops rather than error, and the clamped result is never what anyone intended.
Three stop techniques cover almost everything real:
- Fade to transparent. Use
stop-opacity="0"on a stop of the same colour rather than fading to white. Fading to white through a transparent context produces grey fringing; fading opacity does not. - Hard edges. Two stops at the same offset with different colours create an instant colour change. That turns a gradient into stripes, badge segments or a progress bar with no extra shapes.
- Fighting banding. Visible stepping between two close colours is a colour-distance problem. Add one or two intermediate stops, or widen the colour difference. No amount of file fiddling fixes a six-value crawl across a huge area.
Stops accept CSS too, which matters for theming: an inline SVG can use stop-color: var(--brand) from your stylesheet. The catch is the same one that governs all icon colouring: CSS only reaches stops when the SVG is inline in the page, never through an img tag, the trade-off explained in changing icon colour.
gradientUnits, the decision that causes the bugs
Every gradient coordinate you have seen so far is interpreted through gradientUnits, and its default, objectBoundingBox, treats the bounding box of each painted shape as a 0-to-1 square. That default is convenient and it is behind the two strangest behaviours in SVG gradients.
First, the stretch. Because the shape's box is treated as square whatever its real proportions, a diagonal gradient on a wide rectangle leans much flatter than 45 degrees. Two shapes with different proportions sharing one gradient will show visibly different angles. This is not a bug, it is the coordinate system doing exactly what it says.
Second, the vanishing gradient. A perfectly horizontal or vertical straight line has a bounding box with zero height or zero width. Zero-area box, nothing for the gradient to map onto, so a gradient stroke on that line simply does not paint. It is a classic head-scratcher because the identical gradient works fine on every other shape in the file.
The fix for both is the other mode. gradientUnits="userSpaceOnUse" makes the coordinates real positions in the SVG's own coordinate system, the same user units the viewBox defines. The gradient stops stretching per shape, angles become true, zero-area boxes stop mattering, and one gradient can run continuously across many shapes, which is how a multi-path logo gets a single unbroken sweep instead of a restart on every letter. The cost is that the gradient is now pinned to fixed coordinates, so it will not adapt if the artwork moves or scales within the canvas. The working rule: bounding box for one self-contained shape, user space the moment a gradient must be continuous, angled precisely, or applied to lines.
Reusing, rotating and repeating
Three attributes turn one gradient into a system. href lets a gradient inherit everything from another, so you define the stops once and make positional variants that reference them; in old files you will see this written as xlink:href, the legacy spelling that still works but should not be written new. gradientTransform applies a transform to the gradient's coordinate space, and gradientTransform="rotate(45)" is the honest way to get an exact angle rather than reverse-engineering endpoints. spreadMethod decides what happens beyond the last stop when the gradient vector is shorter than the shape: pad extends the final colour, which is the default, reflect bounces the gradient back and forth, and repeat tiles it forwards, which is the cheapest way to get candy stripes from a two-stop definition.
Gradients on strokes and text
A gradient is paint, and paint applies to strokes as readily as fills: stroke="url(#fade)" puts the gradient on the outline. Remember the outline is a shape with its own bounding box, which loops straight back to the vanishing-line trap above; gradient strokes on straight connector lines want userSpaceOnUse essentially always.
Text takes gradient fill the same way, and it is the rare text effect that costs nothing: real, selectable, searchable text with fill="url(#fade)", no outlining, no images. One decision to make deliberately: with the default bounding-box units the gradient spans each text element separately, so two headline lines restart the gradient each. Switch to user space to sweep one gradient across the whole lockup. The same choice, per letter versus across the word, is exactly what separates amateur gradient logos from professional ones.
Where gradients go wrong
Gradient bugs are nearly all reference bugs, and they fail silently. The checklist, in the order that finds them fastest:
- ID collisions. Inline several icons that each define a gradient called
a, the browser takes the first definition it finds, and every other icon paints with the wrong gradient or turns black. Design tool exports love single-letter gradient ids, which makes collisions near-certain on a page of inlined artwork. Prefix ids per icon, or load full-colour icons throughimgso each file keeps its own namespace, the trade-off covered on the coloured icons page. - Stripped definitions. Optimisers can minify or remove ids and unused-looking defs, and sanitisers on upload pipelines sometimes remove more than that. If a gradient survives on your machine and dies on the server, diff the file that actually shipped. The settings that do this, and the ones that are safe, are in optimising SVG files.
- References across files.
url(#fade)resolves within the current document. Split the gradient definition and the shape that uses it into different files, as external sprite setups do, and support gets unreliable. Keep gradient definitions in the same document as the shapes they paint; the sprites guide covers how defs and symbols interact. - Recolouring expectations. The
currentColortrick that makes flat icons inherit text colour does not restyle a gradient's stops from outside. A gradient icon is a full-colour icon, with everything that implies for theming and dark mode. - Mesh gradients. Inkscape can author SVG 2 mesh gradients and browsers do not render them; a mesh-gradient file ships as a flat fallback or a broken image. If artwork needs mesh-style shading on the web, export it as a raster and accept it, per the honest rules in vector vs raster.
Gradients also animate, and tastefully moving stop offsets is the standard shimmer and sheen technique. The mechanics, and the performance and reduced-motion rules that apply, are in animating SVG icons.
Frequently asked questions
Gradients live inside a coordinate system, and half of this guide quietly depended on it. If user units are still fuzzy, the viewBox is the ten-minute read that makes them concrete.
Writing gradients by hand usually means editing shapes by hand too. SVG path data decodes the d attribute so you can adjust the artwork the gradient is painting.