Anything is possible with SVG, right?!
After a year of collaborating with some great designers and experimenting to achieve some pretty cool visual effects, it is beginning to feel like it is. A quick search of “SVG” on CodePen will attest to this. From lettering, shapes, sprites, animations, and image manipulation, everything is better with the aid of SVG. So when a new visual trend hit the web last year, it was no surprise that SVG came to the rescue to allow us to implement it.
The spark of a trend
Creatives everywhere welcomed the 2016 new year with the spark of a colorizing technique popularized by Spotify’s 2015 Year in Music website (here is last year’s) which introduced bold, duotone images to their brand identity.

This technique is a halftone reproduction of an image by superimposing one color (traditionally black) with another. In other words, the darker tone will be mapped to the shadows of the image, and the lighter tone, mapped to the highlights.
We can achieve the duotone technique in Photoshop by applying a gradient map (Layer > New Adjustment Layer > Gradient Map
) of two colors over an image.


Right click (or alt + click
) the adjustment layer and create a clipping mask to apply the gradient map to just the image layer directly below it instead of the applying to all layers.
It used to take finessing the <canvas>
element to calculate the color mapping and paint the result to the DOM or utilize CSS blend-modes to come close to the desired color effect. Well, thanks to the potentially life-saving powers of SVG, we can create these Photoshop-like “adjustment layers” with SVG filters.
Let’s get SaVinG!
Breaking down the SVG
We are already familiar with the vectorful greatness of SVG. In addition to producing sharp, flexible, and small graphics, SVGs also support over 20 filter effects that allow us to blur, morph, and do so much more to our SVG files. For this duotone effect, we will use two filters to construct our gradient map.
feColorMatrix (optional)
The feColorMatrix
effect allows us to manipulate the colors of an image based on a matrix of rbga
channels. Una Kravets details color manipulation with feColorMatrix
in this deep dive and it’s a highly recommended read.
Depending on your image, it may be worth balancing the colors in the image by setting it to grayscale with the color matrix. You can adjust the rbga
channels as you’d like for the desired grayscale effect.
<feColorMatrix type="matrix" result="grayscale"
values="1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
0 0 0 1 0" >
</feColorMatrix>
feComponentTransfer
Next is to map the two colors over the highlights and shadows of our grayscale image with the feComponentTransfer
filter effect. There are specific element attributes to keep in mind for this filter.
Attribute | What it Does | Value to Use |
---|---|---|
color-interpolation-filters (required) |
Specifies the color space for gradient interpolations, color animations, and alpha compositing. | sRGB |
result (optional) |
Assigns a name to this filter effect and can be used/referenced by another filter primitive with the in attribute. |
duotone |
While the result
attribute is optional, I like to include it to give additional context to each filter (and as a handy note for future reference).
The feComponent
filter handles the color mapping based on transfer functions of each rbga
component specified as child elements of the parent feComponentTransfer
: feFuncR feFuncG feFuncB feFuncA
. We use these rbga
functions to calculate the values of the two colors in the gradient map.
Here’s an example:
The Peachy Pink gradient map in the screenshots above uses a magenta color (#bd0b91
) , with values of R(189) G(11) B(145)
.
Divide each RGB value by 255 to get the values of the first color in the matrix. The RGB values of the second column result in #fcbb0d
(gold). Similar to in our Photoshop gradient map, the first color (left to right) gets mapped to the shadows, and the second to the highlights.
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
<feFuncR type="table" tableValues="(189/255) 0.9882352941"></feFuncR>
<feFuncG type="table" tableValues="(11/255) 0.7333333333"></feFuncG>
<feFuncB type="table" tableValues="(145/255) 0.05098039216"></feFuncB>
<feFuncA type="table" tableValues="0 1"></feFuncA>
</feComponentTransfer>
Step 3: Apply the Effect with a CSS Filter
With the SVG filter complete, we can now apply it to an image by using the CSS filter
property and setting the url()
filter function to the ID of the SVG filter.
It’s worth noting that the SVG containing the filter can just be a hidden element sitting right in your HTML. That way it loads and is availble for use, but does not render on the screen.
background-image: url('path/to/img');
filter: url(/path/to/svg/duotone-filters.svg#duotone_peachypink);
filter: url(#duotone_peachypink);
Browser Support
You’re probably interested in how well supported this technique is, right? Well, SVG filters have good browser support.
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
8 | 3 | 10 | 12 | 6 |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
117 | 117 | 4.4 | 6.0-6.1 |
That said, CSS filters are not as widely supported. That means some graceful degradation considerations will be needed.
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
18* | 35 | No | 79 | 6* |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
117 | 117 | 4.4* | 6.0-6.1* |
For example, Internet Explorer (IE) does not support the CSS Filter url()
function, nor does it support CSS background-blend-modes, the next best route to achieving the duotone effect. As a result, a fallback for IE can be an absolutely positioned CSS gradient overlay on the image to mimic the filter.
In addition, I did have issues in Firefox when accessing the filter itself based on the path for the SVG filter when I initially implemented this approach on a project. Firefox seemed to work only if the filter was referenced with the full path to the SVG file instead of the filter ID alone. This does not seem to be the case anymore but is worth keeping in mind.
Bringing it All Together
Here’s a full example of the filter in use:
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="duotone_peachypink">
<feColorMatrix type="matrix" result="grayscale"
values="1 0 0 0 0
1 0 0 0 0
1 0 0 0 0
0 0 0 1 0" >
</feColorMatrix>
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
<feFuncR type="table" tableValues="0.7411764706 0.9882352941"></feFuncR>
<feFuncG type="table" tableValues="0.0431372549 0.7333333333"></feFuncG>
<feFuncB type="table" tableValues="0.568627451 0.05098039216"></feFuncB>
<feFuncA type="table" tableValues="0 1"></feFuncA>
</feComponentTransfer>
</filter>
</svg>
Here’s the impact that has when applied to an image:

See the Pen Duotone Demo by Lentie Ward (@lentilz) on CodePen.
For more examples, you can play around with more duotone filters in this pen.
Resources
The following resources are great points of reference for the techniques used in this post.
- SVG Filter primitive elements – MDN documentation
- Finessing feColorMatrix – Una Kravets’ detailed post on A List Apart
This is SO helpful! Thank you!!
Thanks for sharing this! I’m going to use this technique when creating a banner for my https://www.kodevelopers.com/5-server-tanitimi/ website! You’re such a lifesaver.
First: awesome article!
As a note, you can increase browser support, if you use inline SVG
<image>
tag, and apply filter directly with itsfilter
attribute.DEMO
☝️
A made a quick demo to test (only tested on IE 11, but should work on 10 as well)
Thanks! Keep shipping!
I had to try and create this effect in a project recently. I said it was impossible. Apparently I was wrong.
Thanks for the great article :)