These days there are a LOT of solutions to the icons problem. You can simply go ahead and pick a solution you like and use it.
However there is one hickup in this: almost all the solutions these days are based on inline SVGs. This means a few things:
- Icons are rendered to HTML when doing SSR. Very nice for initial render!
- Icons are rendered to DOM on client-side.
- Client-side render is based on components, such as ones based on Virtual DOM in React. Some solutions like Iconify use
dangerouslySetInnerHtml
which is slightly better (but then no SSR). - The icons are JavaScript code, or rely on JavaScript to function.
The good thing with inline SVG is that you have full control over the internals of SVG file. Need to change a color? Just set fill
and you're good to go. Heck, since "it is just code" you can even write some of your own code to do modifications to the SVG before rendering it.
But when icons are code there are performance implications. You need to ensure icons are only included in your bundles when the icons are needed on page. So then you need to setup lazy loading of these components, at which point you start getting awful amount of complexity since on SSR you want to have the icons included in HTML, but then you have the icon also included in the client JavaScript bundle only for the sake of things matching on hydration. So you're serving the icons twice and doing JS processing for them. On SPA page loads you only load icons you need, but they're still code which is more expensive than pure image asset.
Can we do better?
The Options
There are many ways to embed SVG images to pages. The reason inline SVG "won" in the past was due to being compatible with old browsers like Internet Explorer 11 while allowing CSS access to changing colors. The other means to embed SVG include solutions like <img />
, <object />
, CSS background or mask, and <svg><use /></svg>
. Of these img
, object
and CSS embeds do not provide any further access to the SVG internals: there is no way you can control the style of an SVG file.
But there is one technique that allows you to style the internals: the <use />
element. But you don't get direct access to styling via fill
or stroke
. Instead you only have access through indirect access via things like currentColor
or CSS variables. The latter means no support for the old browsers, evergreen it must be.
Interested? Lets have a look!
Gotchas of <use />
The nice thing about <use />
is that you can give a href
of any kind. So you can switch between two ways to embed: a regular URL path to SVG file, or inline the SVG using data:
URL. This provides some flexibility! Also, since we are declaring an actual <svg />
element in our HTML we can also provide a <title />
element to ensure accessible context.
The downside is that unlike <img />
element there is no native lazy loading available. This sucks and you're forced to implement lazy loading through some form of JavaScript.
To make this method work well there are a bunch of requirements:
- You must give element an id, such as
id="icon"
. - You must not have
width
orheight
defined in<use />
element, or in the actual SVG file. - To allow manipulating color you have to replace all
fill
andstroke
values in the SVG withcurrentColor
. - Better yet, you can pick a CSS variable name like
--icon-color
and use that for all the colors.
Sample code
Here is an adapted Facebook logo with minimalistic code.
<svg id="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<path d="M40,0H10C4.486,0,0,4.486,0,10v30c0,5.514,4.486,10,10,10h30c5.514,0,10-4.486,10-10V10C50,4.486,45.514,0,40,0z" fill="var(--icon-color,#1877f2)"/>
<path d="M39,17h-3 c-2.145,0-3,0.504-3,2v3h6l-1,6h-5v20h-7V28h-3v-6h3v-3c0-4.677,1.581-8,7-8c2.902,0,6,1,6,1V17z" fill="var(--icon-light-color,#fff)"/>
</svg>
A notable thing here is that we've made a two color icon. It displays correctly and uses Facebook brand colors when viewed normally in a browser. I've chosen here to use --icon-color
for the "main" color, and --icon-light-color
to basically indicate a white color. Using two different variables lets us manipulate both via CSS individually.
The HTML is a rather short:
<svg width="4rem" height="4rem">
<title>Facebook</title>
<use href="facebook.svg#icon"></use>
</svg>
Not really that much more code than with an <img />
tag :)
With this code we can now style the icon itself:
svg {
--icon-light-color: rgba(255, 255, 255, 0.75);
--icon-color: currentColor;
}
:hover > svg {
--icon-color: unset;
--icon-light-color: unset;
}
Here is a demonstration as an extended Codepen with comparison against <img />
to show that you can't control color when embedding with it, but can via <use />
.
Color transitions
There is still one more thing that people might like to control: animating the colors. With this feature we're still limited, because only Chromium based browsers support @property
which allows us to animate CSS variables as colors. I actually implemented this to the Codepen above (see style element in the HTML), but for some reason it didn't work there. I don't know why, maybe data URLs? I did get this to work while experimenting outside Codepen.
Summary
So what do we get by doing things this way?
- No layout shifting as icon always has the space it needs reserved for it
- A much more web standards take: HTML remains the same before and after hydration
- Icons are resources, not code, and you can't fail with that
- No icon contents duplication to JS bundles (except if used as data URL)
- You can inline or use as external, no changes or special cases to the actual icon component implementation
- Color can be manipulated if icon uses
currentColor
or CSS variables
But what is not so nice?
- All current icons out there are incompatible: you need
id="icon"
in the SVG file - Less pure fine grained control via CSS: everything has to be CSS variablized
- Color transitions lack full cross-browser support
- Lazy loading requires JavaScript
- There is no community or any kind of standardization around this solution
So what do you think? Is this worth more effort? There are many ways one could set this up, but building a helpful structure and process around this would need quite a bit of work. Any attempt for a successful adoption would need creating components for multiple frameworks and provide preprocessed SVG icon files.
Top comments (0)