Hiding in plain sight
In SVG, elements have a fill and possibly a stroke.
<svg viewBox="0 0 1 1">
<rect fill="goldenrod" width="1" height="1" />
</svg> You can set the attributes to a solid value, but it is also possible to add a gradient.
<rect fill="url(#gradient)" width="1" height="1" /> You need to build the gradient first, and for this, there are two pertinent tags: <linearGradient> and <radialGradient>.
Focusing on the first of the two, define the gradient separate from the shapes.
<linearGradient id="gradient">
<!-- ... -->
</linearGradient> And describe the colors with <stop> elements.
<linearGradient id="gradient">
<stop stop-color="white" offset="0" />
<stop stop-color="goldenrod" offset="1" />
</linearGradient> Each stop needs but two attributes: stop-color and offset. The color is rather intuitive, and points to the value assumed by the gradient at the precise spot. The offset falls in the 0 to 1 range, but you can also use percentages, from 0% to 100%.
The code is solid and quite understandable. Applied on the rectangle, you end up with a gradient from white to goldenrod.
But what if you wanted to cast a proper light? You can add a bright layer.
<rect fill="goldenrod" width="1" height="1" />
<rect fill="url(#gradient-light)" width="1" height="1" /> And if you have the gradient go from white to transparent, you brighten up the shapes below — the rectangle, and any other shape you may want with it.
<linearGradient id="gradient-light">
<stop stop-color="white" offset="0" />
<stop stop-color="transparent" offset="1" />
</linearGradient> This is where you stumble on an interesting SVG quirk. The gradient starts as white, but disappears with a rather dark undertone.
And if you are not convinced, let me try to replicate the feat with regular CSS.
div {
background: goldenrod;
background-image: linear-gradient(white, transparent);
} Set the background and the gradient on a sizable <div> element and the result is undeniably different.
Aside from the direction of the gradient — CSS works top to bottom, SVG left to right — the empty container ends up much brighter.
Why is that? In SVG, transparent doesn’t actually mean transparent, but “transparent-black”. If you are familiar with the HSL color space, this means the gradient goes from a fully opaque white — hsl(0 0% 100% / 100%) — to a completely invisible black — hsl(0 0% 0% / 0%). In between, the bright color is shaded in darkness.
To solve the issue there are at least two ways, a roundabout manner, and a more obvious choice. And it is only natural to start with the first, if only to appreciate the second a tad more.
Mask
The <mask> element helps to conceal parts of a vector. Define the element similarly to the gradient.
<mask id="mask">
<!-- ... -->
</mask> In the body of the element decide what to show and what to hide with additional shapes. What is visible depends on color, and if you restrict yourself to black and white, the logic is straightforward: white areas are shown in full, black areas are removed from view. In between, what you see depends on lightness.
You may start to see how this is helpful for our lighting needs.
If you add a white layer, instead of the ineffective gradient.
<rect fill="goldenrod" width="1" height="1" />
<rect fill="white" width="1" height="1" /> You need only to mask the element so that the rectangle disappears.
<rect fill="goldenrod" width="1" height="1" />
<rect mask="url(#mask-light)" fill="white" width="1" height="1" /> It is the job of the mask, then, to progressively hide the shape as it nears its end. How? Why, with a gradient.
<mask id="mask-light">
<rect fill="url(#gradient-lightness)" width="1" height="1" />
</mask> A gradient gradually moving from white to black.
<linearGradient id="gradient-lightness">
<stop stop-color="white" offset="0" />
<stop stop-color="black" offset="1" />
</linearGradient> This is likely the biggest leap of logic in the entire sequence, so it might help to consider the steps top to bottom. You have a gradient, gradually moving from white to black. You have a mask, and with the gradient, this mask works to hide whichever shape you want, little by little. You have a white rectangle, and with the mask you remove its luminous influence.
Twisted, but ultimately effective.
Opacity
Let’s go back to the feature at hand, adding a light with a gradient, moving from white to transparent. If the transparent keyword leads us astray, we just need to be more specific. What type of transparent? Well, transparent white.
CSS tips a solution with the many available color formats. rgb, hex values, hsl. These have all an option to change the opacity.
Update the final color to be a completely translucent white and you’re done.
<linearGradient id="gradient-light">
<stop stop-color="hsl(0 0% 100% / 100%)" offset="0" />
<stop stop-color="hsl(0 0% 100% / 0%)" offset="1" />
</linearGradient> Of course SVG can’t stand idly by, and if you want to stick to named colors, you find a way going back to the very beginning.
In SVG, elements have a fill and possibly a stroke
In SVG, you can change the opacity of entire elements with the opacity attribute. Matching the pair, however, you can also affect the opacity with two, more specific strings: fill-opacity and stroke-opacity.
On top of these two, SVG offers a third attribute, and with luck it is named stop-opacity. Set the value for the final stop to 0 and the white disappears.
<linearGradient id="gradient-light">
<stop stop-color="white" offset="0" />
<stop stop-opacity="0" stop-color="white" offset="1" />
</linearGradient> Leave it to CSS, lean on SVG, either solution works. Either solution shines a proper light.
And at this point you can skip the twisted logic and the <mask> element. Or, rely on the element for its intended use, to hide and show parts of the vector. Elaborate and illuminating how it might be.