TL;DR
- Flatten your SVG icons so that there are no intersections between paths.
- If that is not possible and if you can afford to, convert all strokes to fills by using e.g. Really Flatten Vectors.
- If that is not possible, use SVG masks to render the icon (check the code at the end of the article).
The Problem
If you ever worked with colours whose alpha (opacity) value is not 0 or 1 before, you probably faced this nasty problem where paths with a semi-transparent opacity creates areas of different opacity at intersection points. For example, let's take two icons from Lucide that suffer from this problem:
The reason for this is that different paths are drawn separately and hence can overlay on existing semi-transparent paths, causing intersections where the opacity is "doubled". In the above figure, flask-conical
has three paths (the flask shape and the two horizontal lines), while atom
has two oval-shaped paths if we exclude the circle at the centre. The paths are drawn separately, hence all intersections between these paths are "whiter" than the rest of the icon.
I faced this problem when trying to handle icons in my personal website (the very website you're looking at), since most of the colours you're seeing here are semi-transparent. This article will describe the two ways I found to effectively solve this problem. Note that this assumes the icon is in a single colour – it won't work if your icon is duotone.
Flattening
For most icons, this is the simplest and the best solution. Flattening is the process where you combine several paths into one. For example, for the chevrons-right
icon , the SVG paths are:
but after flattening, they become one single path:
Since the two paths have become one, they are drawn at the same time, hence there are no intersections for the opacity to overlap. The same applies for the flask-conical
icon above, where the three paths can be flattened into one:
Although, clearly the paths are basically computer-generated unreadable gibberish now. If you want to be able to read and debug your icon SVG in code directly, you might want to also save an unflattened version of the icon. Kinda like the source code of a minified JavaScript file.
It's pretty simple to perform this flattening. I think all vector graphic editors should have this feature. I use Figma specifically for this – you can simply CTRL+E (or ⌘+E on Mac) to flatten any selection. Other editors should have similar features, and online tools should probably do well too.
Convert Strokes to Fills ("Really" Flattening)
Sometimes, however, simple flattening like above doesn't work. For the atom
icon above, the two oval-shaped paths can't be flattened into one.
If it is possible for you to do so, you can consider changing strokes to fills. A lot of CSS change may be needed to re-style the icons correctly, and you won't be able to customise stroke width and similar stuff with code anymore, but if all of those are not a problem for you, this is a good solution.
For example, with the atom
icon, by applying the Really Flatten Vectors Figma plugin, and then put the SVG through SVGOMG optimisations, I get this absolutely crazy result:
The d
value is almost 2k characters long, but it does render the icon correctly!
SVG Masks
If both of the above methods don't work for you, you can try using SVG masks.
In this approach, instead of rendering the icon directly, we use it as a SVG mask for which part of the SVG we want to render (black pixel in the mask → render, white pixel in the mask → don't render). Then we simply apply that mask on a rectangle filling the size of the icon. Naturally a rectangle can't intersect itself, so we won't see that opacity issue.
For example, this is the SVG for the original atom
icon:
then you can use the paths of this icon to construct a mask (note that in this mask, the background is black and the foreground (icon paths) are white):
and then apply that mask on a rectangle filling the size of the icon:
Now as you can see, we still use fill="currentcolor"
here. But if you want, you can always use a stroke of width 24 to fill the rectangle, since it's only a simple rectangular shape and not a complex 2k-character shape now. Another good point is that you can customise the stroke width and similar stuff with code now.
Of course, this step is more complicated and there's actually an apparent Safari bug where it makes the quality of the SVG drop when you pinch zoom in (if you are on a Mac with a touchpad right now, you can try pinch zooming the above figure and see). But if you really can't flatten your icon, such as the use of atom
inside my website that you are looking at, this is the only solution that I can find.
Happy coding!