DEV Community

Cover image for Letting Product Teams Own SVG Icons (Without Code Changes)
Bojan for Subito

Posted on with Alessandro Grosselle and Tommaso Ruscica

Letting Product Teams Own SVG Icons (Without Code Changes)

At Subito (Italy's leading classifieds platform), we recently introduced a smart way to load and dynamically color SVG icons, one that solves a product need, not just a technical one.

You might think: "SVGs? Nothing new there." And you'd be almost right. The twist is why we built it this way.

The Problem: Decoupling Icons from the Dev Team

We were building a new feature: User Badges, visual icons displayed on the user profile and on the ad details page, to highlight seller qualities (e.g. fast responder, top seller, verified).

The requirement was clear: the product marketing and marketing teams needed to own and update these badge icons independently, without involving the dev team every time a new badge was introduced or an existing one changed.

On top of that, the icons couldn't just be static, they also needed to adapt their color dynamically.
At Subito, each category has its own brand color (e.g. motors is blue, phones is yellow), and the same badge could appear in different category contexts.
The icon had to match the surrounding palette automatically.

So the question became: how do you load an SVG dynamically and color it at runtime?

The solution: CSS mask-image

Step 1: host SVGs on S3

The first part is straightforward: upload the SVG files to an S3 bucket with public access.
This gives product teams a simple upload interface to swap icons anytime they want.

Step 2: color them dynamically with mask-image

Here's the smart bit: instead of loading the SVG as an <img> (which can't be styled) or inlining it (which couples it to the codebase), we use the CSS mask-image directive.

mask-image lets you use an image (including an SVG) as a transparency mask over an element. Only the shape of the SVG is visible, you control the fill via background-color.
Since we set that to currentColor, the icon inherits the color of its surrounding context automatically.

const maskStyle = (file: string): React.CSSProperties => ({
  maskImage: `url(${ASSETS}/static/icons/badges/${file}.svg)`,
});

export const SvgIcon = ({ iconName, className }: Props) => (
  <span
    className={classNames(styles.maskIcon, className)}
    style={maskStyle(iconName)}
    aria-hidden="true"
  />
);
Enter fullscreen mode Exit fullscreen mode

And the CSS module:

.maskIcon {
  background-color: currentColor;
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
}
Enter fullscreen mode Exit fullscreen mode

That's it!

Change color on a parent element and the icon follows. The product team uploads a new SVG to S3, and it's live, no code changes, no deploys.

You can play with this yourself on CodePen: https://codepen.io/ale-grosselle/pen/ogzapXa

And here's the result in production:

Is this our standard for all SVGs?

Not exactly. This approach is our standard for updated icons owned and managed directly by product teams.

For everything else, we still use the classic approach: static SVG files committed to the repo, imported directly as React components.

These icons don't change often, don't need external ownership, and are better off living directly in the codebase as React components.
Bundling them avoids unnecessary HTTP requests and the latency of fetching assets from an external bucket; they're just there, instantly.

We use SVGR to automate the conversion; under the hood, it runs SVGO to optimize the SVG before generating the component, stripping unnecessary attributes and reducing file size.

A single script in package.json handles everything:

"generate:icons": "svgr --out-dir generated svg"
Enter fullscreen mode Exit fullscreen mode

Drop an SVG into svg/, run the script, and a ready-to-use React component appears in generated/.

If you want to explore the code, here's the GitHub repo: https://github.com/Subito-it/articles-code/tree/main/icons

Conclusion

Our approach to loading SVGs depends on the use case:

  • Icons that need frequent updates by non-developers: S3 bucket + CSS mask-image for dynamic coloring
  • All other icons: Static files in the repo, converted to React components with SVGR

There is rarely a single "right" solution in engineering; the best answer depends on the use case. That's exactly why at Subito we ended up with two coexisting approaches for loading SVGs: not out of inconsistency, but out of pragmatism. Each one is the right tool for its specific job.

Top comments (10)

Collapse
 
totallyinformation profile image
Julian Knight

This seems rather an over-complex solution that isn't necessary since SVG's themselves are easily styled directly using CSS. So why not simply specify your SVG's to use CSS variables? I do this on a tool that I develop and it works excellently. These days SVGs are first-class styleable parts of the web.

Collapse
 
alessandro-grosselle profile image
Alessandro Grosselle Subito

How would you color an svg taken via url, managed and checked by the product team?

Collapse
 
totallyinformation profile image
Julian Knight • Edited

By adding the CSS variable setting to a suitable override. Either in a style sheet or just as a style attribute in the img tag. Or using JavaScript. Any of those should work.

Thread Thread
 
alessandro-grosselle profile image
Alessandro Grosselle Subito

ok, so, using this example: codepen.io/ale-grosselle/pen/ogzapXa

Color assets.subito.it/static/icons/badg... without using mask-image

Thread Thread
 
totallyinformation profile image
Julian Knight

Try this:

codepen.io/editor/totallyinformati...

Just change the CSS var either in the stylesheet or as a style attribute.

Thread Thread
 
totallyinformation profile image
Julian Knight • Edited

Try this:

codepen.io/editor/totallyinformati...

Just change the CSS var. Either in the stylesheet or in a style attribute. The SVG has an embedded default so you don't have to have the var at all. You just add it when you want it. Should work just fine loading the SVG on an img tag as well.

CSS - not just for HTML! 😀

Thread Thread
 
alessandro-grosselle profile image
Alessandro Grosselle Subito

Buddy, you need to read the article because you really didn’t get it!

Precondition: the SVG must be loaded as an external resource, because the product team has an S3 bucket where they upload them.

Try to color this asset: assets.subito.it/static/icons/badg...

Thread Thread
 
totallyinformation profile image
Julian Knight

I'm afraid that you've missed my point. The SVG you share does not contain a CSS variable. If it did ....

Actually, let me create a better example. Codepen doesn't let me create more files so bear with me while I put something together.

Thread Thread
 
totallyinformation profile image
Julian Knight

OK, so that was a deep rabbit hole!

Turns out that I'd made some incorrect assumptions. Where I'd used this trick was not quite the general solution I'd assumed though it worked for me.

So I've put together a more comprehensive test repo here: github.com/TotallyInformation/svg-... which you can view online here: totallyinformation.github.io/svg-c...

In the end, I found 3 things that will work with SVG's and CSS Variables. One of them based on your article (which rather limits the styles and images you can use - works for your icon example but not necessarily for all use-cases).

There is another JavaScript based solution by simply importing the SVG and inserting to the DOM.

The final solution which I think might be my favourite actually as it requires no JavaScript, is to embed an SVG in the HTML that uses the external SVG file as an import.

<svg style="width:2em;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
    <use href="./blue-world.svg" />
</svg>
Enter fullscreen mode Exit fullscreen mode

These last 2 options allow full use of style overrides in the remote SVG including using CSS variables. They also allow more flexibility than the mask image option I believe.

Interesting. Thanks for your article. I've learned a lot! 😀

Some comments may only be visible to logged-in visitors. Sign in to view all comments.