Icon System with SVG Sprites

Published
March 12, 2014
by
Chris Coyier

I've been a big proponent of icon fonts. Lots of sites really need a system for icons, and icon fonts offer a damn fine system. However, I think assuming you're good with IE 9+, using inline SVG and the <use> element to reference an icon is a superior system.

First let's cover how it works.

A nice way to handle your icons is to have a folder full of .svg files.

That's one of the cool things about working with SVG - they are the source files.

They can be colored, not colored, multiple shapes, sizes, whatever.

You can let Illustrator (or whatever) save it however, with all the cruft that comes along for the ride:

Combine the .svg files

You can manually do this if you want. I've done it. You don't even have to look at the final file. Just call it svg-defs.svg or something.

It should just be an <svg> tag, with a <defs> tag (which just means you are defining stuff to use later), and then a bunch of <g> (group) tags. Each <g> tag will have a unique ID, and will wrap all the paths and whatnot for each icon.

<svg>
<defs>
<g id="shape-icon-1">
<!-- all the paths and shapes and whatnot for this icon -->
<g>
<g id="shape-icon-2">
<!-- all the paths and shapes and whatnot for this icon -->
<g>
<!-- etc -->
</defs>
</svg>

Inject that SVG at the top of the document

It's gotta be at the top, sadly, as there is a Chrome bug in which this isn't going to work if defined later. Although... there is more to this story because as I type these words, the theme this very site is using has the icons defined at the bottom of the document and it works. Ughkgh confusing.

Another way: IcoMoon

IcoMoon, which is known for producing icon fonts, actually does a fantastic job of producing SVG sprites as well. After selecting all the fonts you want, click the SVG button on the bottom and you'll get that output, including a demo page with the inline SVG method.

Browser Support

On the browser support front, the danger zones are IE 8 and down, Safari 5 and down, iOS 4.3 and down, and Android 2.3 and down. But if your policy is "the last two major versions" - you're looking at pretty much 100% support.

Remember that icons can be used as a supporting role only, like always accompanied by a word. If that's the case, support isn't too big of a deal. If these are stand-alone, and non-display would make the site unusable, that's a big deal.

I probably would go for icon fonts, as the support there is much deeper. Just make sure you do it up right.

This is going to get a lot better

This does work in some browsers, meaning you could skip the include at the top of the document. Doing it this way means an extra HTTP request, but that means you can utilize caching more efficiently (not bloat document caching). In testing, Jonathan Neal discovered you need to have the xmlns attribute on the <svg> for it to work:

<svg xmlns="http://www.w3.org/2000/svg">

But even then, no support in any IE. Unless you wanted to swap out the whole <svg><use> with an <object>, which does work. Jonathan Neal again figured this out:

"Versus" icon fonts

Weird failures: SVG seems to just work (when supported). Icon fonts seem to fail in weird ways. For instance, you map the characters to normal letters, then the font loading fails and you get random characters abound. Or you map to "Private Use Area" and some browsers decide to re-map them to really weird characters like roses, but it's hard to replicate. Or you want to host the @font-face files on a CDN, but that's cross-origin and Firefox hates that, so you need your server to serve the right cross-origin headers, but your Nginx setup isn't picking that up right, SIGH. SVG wins this one.

Semantics: Not a huge deal, but I think an <svg> makes a bit more sense for an image than a <span>.

Accessibility: Maybe someone can tell me? Can we/should we give the <svg> a title attribute or something? Or a <text> element inside that we visually hide? Update: the <title> element might do. Or perhaps the <desc> element as used in this SVG access spec.

Ease of use: Tools like Fontello and IcoMoon are pretty good for an icon font workflow, but the folder-full-of-SVGs with Grunt squishing them together for you is even easier, I think.

Ian Feather posted an article about why they switched away from icon fonts as well and I agree with every single point.

Thanks for this.
I thought it was worth adding that instead of mapping an @font-face icon to PUA or normal letters, there’s also the possibility of mapping them to similar Unicode equivalents.
When used together, you get quite reasonable browser support:

I think one of the big things that draws people to icon fonts is the huge amount of icon sets. For people not able to design their own icons, this is super important. I’ve noticed a lot more great SVG icon sets lately though. A good round-up of SVG icons would be cool, like the one you did for font icons.

I still prefer icons because I can style them like text and style them WITH the text that they’re next to (color, shadow, line-height). This is especially useful for hover effects on buttons and if the client wants to re-size or recolor on a whim, it’s cake.

Granted though, it really depends on the project and sprites are more applicable to some projects than others.

What are the ramifications of using an IMG tag and targeting the .svg file as the src? e.g. <img src="my-icon.svg">. I’ve tested on CodePen to verify this works (at least in Chrome). Is there something inherently wrong with this?

There is nothing wrong at all with that. I do it all the time. But with an icon system, you probably wouldn’t want to do that the dozens/hundreds needed for each icon. The goal is speed and reducing HTTP requests by having just one (or none) files that contain all the glyphs together.

It’s actually easier than that for real world (non-CodePen) situations. Just put all your icons into one SVG file, and reference the filename along with the fragment in the use element. The SVG file should be cached by the browser just like any other image resource used by multiple pages.

But that doesn’t work if your “SVG file” is actually a link to a CodePen page, not an actual file. And it also doesn’t work (in most browsers with default security settings) if the file you’re linking to isn’t in the same domain as the file with the <use> element linking to it. “Domains, protocols and ports must match,” says the error on my Chrome console from the example I was trying to put together.

I don’t like how despite the benefit of using SVG you still have to work with pixels to specify icon dimensions. I played with svg sprites a couple of weeks ago and came up with an interesting solution.
I tested it on this this sprite-generating package and it works pretty well.

Well, you don’t have to use pixels (although CSS pixels are always there under the hood). You can size the wrapping element in any css unit (em, rem etc) and then size the element inside in percentages.

Chris’s method has an arguable performance advantage over your svg-as-css-bg because in his the path data is loaded immediately inline, immediately, whereas in yours the SVG file doesn’t even get requested by the browser until CSS is loaded and parsed. Background images generally having a way of arriving last–which might be what you want, depending on your priorities.

Highly unlikely. Positioning everything with SVG is essentially the same as positioning everything with absolute values. And although you can embed auto-sizing foreignObject elements (ignoring the current buggy-ness of implementations), you can’t easily (without scripting) adjust the rest of the layout accordingly.

Referencing fragment identifiers from your CSS could cause issues. Opera and FF needs path with the main svg file – url(icons.svg#element), while Webkits and IE needs just el id -url(#element). We could make it work by code preprocessing and then conditional CSS loading, but it is not that elegant though.

It may, in some cases, be required to set the namespace for xlink to work. It should come out looking something like<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">

Good point. If you need an xlink:href within a stand-alone SVG file, you need to specify the namespace. When the SVG code is inlined in an HTML 5 document, you can skip the declaration, so it’s easy to forget.

This is awesome! However, adding the SVG to the HTML basically means no caching of the SVG (which can be a very large file if you need many icons); but I think there’s a simple solution for it:

<use xlink:href="RelativeLinkToSVGFile.svg#tag"></use>

We can use a relative link to the SVG file followed by the id. I made a demo page for testing. It seems to work perfectly everywhere; but if you try it locally, it fails in Chrome, with an error like this:

When hosted on a server, it’ll work in Chrome as well. This approach solves the caching problem of putting the SVG in the HTML, and makes the HTML much cleaner too.

I updated IcoMoon’s sprite generator today after reading this article. If you use IcoMoon to generate your SVG sprite (using the SVG button rather than Font), it puts the SVG inside the HTML because otherwise, the demo won’t work locally in Chrome, which would be confusing. So I’m hoping to find a solution for Chrome. Maybe it’s just a bug?

Yeah I’ve seen that too. I just made a local domain to test it and it works fine. It’s just the same kind of thing as needing a real domain to do ajax testing.

The real strike against linking to the .svg file right from <use> is that it fails in IE-anything. I updated the article with some stuff from Jonathan Neal that seems to work. See his demo: http://sandbox.thewikies.com/svg/

You can use styles to target the icon as a whole, but not individual components within it; the styles will be applied in any case where inherited styles aren’t over-written by more specific styles.

So you can change the default fill color and the default stroke color (and any other properties, like fill-opacity or stroke-opacity), but any parts of the icon which don’t use default values won’t change. You can also take advantage of the fact that you can use the keyword currentColor within the SVG to grab the value of the CSS color property in effect for that element, and so you can use that to create differently-coloured regions which you can control from the CSS.

All of the examples I found go straight in to options{} and files{} but in my case I had to enclose them in default{} – finally figured this out by checking out the gruntfile.js within node_modules/svgstore itself.

I’m fairly new to coding I’m not sure if this method will work on the Opera Browser, I come here to look and learn css code from you guys. Keep up the good work guys some of your tactics help me put my website together. Thanks guys

The real pain here is not being able to use and style SVGs as pseudoelements. Often, Icons are purely visual, and are coded as :before and :after. With svgs, we have to do it as a background image and therefore use the ability to style with css.

I need to play around with what’s possible when using SVGs as pseudo elements or background images (defs, hover styling etc.). If we can achieve the same level of control as with inline SVGs then happy days!

It really depends on the image, and what you’re comparing it with. Some types of graphics would require large PNG/JPEG files but can be quite compact when described as SVG, so that’s a bonus both for downloading as well as for use. It also depends what you’re going to do with it. Firefox in particular seems to have a hard time moving SVG images around — even when you’re not changing the image at all, the browser seems to recalculate the layout every time. This is a problem if using a set of SVG icons as a conventional sprite sheet controlled by background position properties, and another reason to use inline SVG with <use> elements for SVG icons.

I agree with Amelia, it depends on what you are doing. SVG tends to be slower then plain raster image, but in many cases it could be faster compared to 2x image resized to 1x for hight pixel density screens. From my experience point of view, I’ve launched about 4 large project with technique described in this post and never had problems with it. Christophe Schwyzer have made interesting tests that could be interesting for you.

For accessibility, I love the idea of using font icons with ligatures defined for entire words. Screen readers would read the icon as a regular word, and even copy-paste would give you the text version. It could be pretty nifty for something like a simple contact card with phone, email, etc.
Of course, support is another matter.

This is great and cool at the same time. But as I decided to try this on my own, I did the combining by hand (because I like to do it and mess terminal up really easily) and then did the php request after the body tag as Chris has above. The images load where I want, but there’s a huge chunk of white space now at the top of the document where the svg file is being brought in. Am I doing something wrong or am I missing some code somewhere to not make the svg file render on the screen as a huge chunk of space?

Transitions working as usual.
Animation don’t working across browsers so you would probably need to use js to animate your icons.
You can’t use CSS transforms on SVG in FF and IE, so obviously you can’t animate and transit them too.

I made a Pen as a personal test. I thought I’d post it in case it’s helpful to other readers. I created the vectors in Illustrator and grouped the shapes I wanted to have the same colors, then I named the groups which became classnames. It helped me to do it graphically to understand what I was grouping together.

I have a folder full of SVG icons in my src folder. I run them through gulp-svg-sprites using defs: true, to produce a sprite.svg file in my public folder. Then I generate the HTML from a jade template in my src folder which includes the SVG sprite at the top:

include path/to/svg-defs.svg

Note: It’s really difficult to post comments with code in without getting errors from Securi

It seems like SVG icons can no longer be cached the same way sprites or other referenced resources could be. For example, if I have 100 different webpages which all use the same set of icons, how do I avoid sending those icons back to the browser 100 times?

Also, even just for a single webpage, if any amount of it is dynamically created, the browser will be fetching the same static SVG every time the page is loaded.

If you don’t need to support IE-anything, you can do <use xlink:href="sprite.svg#icon> and it’ll be cached like anything else. Otherwise yes, some page cache bloat, but certainly not a showstopper, at least for me.

First of all many thanks for this amazing article. I’ve been using png sprites via compass for a few years and I’m happy to see svg is finally kicking in.

However, I’m a bit worried though – when including two or three icons there’s no question about performance here, but when you have a sprite with 20-30 svg icons and you include them all as markup in your html.. won’t that cause heavy performance issues?

Thanks so much Chris! This is a really great article!!
For those using Illustrator to create svgs – If you make your svgs on separate layers, there is a script you can install in Illustrator that will allow you to export the layers as individual svgs. This helped make my workflow much quicker. I then uploaded my svgs to Icomoon, then generated the svg sprite rather than the font.

Chris, I totally agree with you when you say,“I imagine someday straight up linking right to the .svg will be the way to go.”

The article says that the sprite-sheet has to be included at the top of the document, right after the body tag, but noticed that CSS-Tricks actually has them at the very bottom of the body. So which one is it?

I think what happened here is that an earlier version of Chrome had as issue with this. They had to be at the top otherwise <use> instances below would fail. I remember confirming it, but I can’t now. So it’s either fixed or I’m crazy. Anyway, it’s better at the bottom IMHO because it doesn’t hold up rendering (probably) more important content.

Although, linking to it externally is even better and I’ll be writing about that soon.

Where icon fonts also fail is when font antialiasing is turned off in system. Some users (like me) like non-antialiased fonts better (for the sake of the eyes) and I even turn off remote fonts rendering in browsers, because many of web fonts are poorly designed for a non-antialiased scenario (my favorite is PT Sans, works like a charm in both cases).

In this setup some icon fonts fail to deliver right icons and show squares. This might be browser-rendering related (for example, Chrome somehow manages to still correctly show font-based icons on some webpages where firefox fails mostly everywhere) , but still…

Chris, I noticed that you said you would be writing about how you could link to your SVG externally. Any idea how soon you would be discussing this, I am really interested in knowing what the proper way to do this. In your example you embed the SVG at the top using PHP, what would be the proper method for doing this if you were only using HTML and PHP is not an option.

I’m planning to switch from PNG sprites to SVG sprites for a new project. I’m exporting the assets, a few simple icons and a logo from Illustrator CC and found that the file size is HUGE and I mean it. The whole PNG sprite is barely 8KB. The same SVG sprite is 3.5 Mb!!! Individuals icons score at 4 KB when exported in PNG and 40-50 KB when in SVG. This is insane. Heck I even tried to export the most simple shape (a plain square with a color fill) and it occupying 4KB!

I assume that I’m doing something wrong cause I don’t find this sizes to be normal. However I can’t find what it is. Export option seem normal and set to a minimum.

Further research shows an unexpected behavior: The main art board is 1200×5000 px aprox. within it is where the icons sit. If I try to export an icon in place (i.e. delete de main art board > wrap the icon in its own art boar by double clicking with the art board tool > Then export selected art board as SVG) the result is a 3.5 MB icon. However if I do this: Copy the icon anywhere outside the main art board > Then wrap the icon in its own art boar by double clicking with the art board tool > Then export selected artboard as SVG. Everything is fine the resulting icon is <4KB.

It is pretty weird since in both cases I end with a single 60x60px art board so the exported asset should be of the same size. It seems as if Illustrator were exporting the original 1200×500 art board even if it doesn't exit anymore.

Anyhow I've moved this questions to Adobe forums. I'll keep you posted if I find more…

I’ve had a terrible time with Illustratir and SVG. Mostly nailing decent export settings. If I don’t export preserving Illustrator editing capabilities then upon re-opening the SVG the artwork is all over the place. However if I maintain that setting I get a bigger file that won’t run through svgo for optimisation.

After exporting my SVG sprite from Illustrator, I opened the SVG with sublime text and cut out a ton of unnecessary cruft. My SVG file went from around 70k to about 40k. One thing you may need to do in order to reduce your SVG file size is reduce the geometry of the icons. This may be a matter of unifying, subtracting, creating outlines of items. Also, sometimes using the simplify path tool can help too. (Obviously you have to be careful, not to destroy the fidelity of your icons in this process.)

One quick way to know if you are reducing the geometry is to go to save > svg > svg code and do a quick review to see if you reduced the geometry or increase it. Also, one other major way I cut my SVG down, was there were in some cases instances of base64 coded embedded images in there. Maybe some can explain why they are in there, but I removed them and didn’t see any consequences from removing them.

What this article doesn’t mention is if you want to use the CSS fill property to control the colour of the outputted icons, you’ll need to remove any instances of inline fill="..." from the compiled SVG file. I’ve had a lot of headache trying to do .icon-x { fill: red; } to not avail because my SVG symbols had inline fills that were overriding my CSS. Hopefully someone finds this useful.

Aren’t svg fragment identifiers not supported well, even on the latest and greatest mobile browsers (ie. Android 4+, iOS 6+)? I see problems in both where the elements simply don’t display consistently; sometimes they show up and sometimes they don’t.

I have come across an interesting bug (?) whereby if you embed the SVG using ‘defs’ as part of a link (an icon for example) – the icon itself does not register a click event in jQuery, but clicking the text does. I think this is due to SVG events not bubbling up?

If you embed the SVG directly, the link triggers regardless of whether you click the text or the icon.

Hey Chris, for me the php include_once command just does not work.
What can I do?

This comment thread is closed. If you have important information to share, you can always contact me.

Treehouse is where you go to learn HTML, CSS, and how to build iOS apps. It's a complete education in modern web and app technology, designed to get you ready for a hot new job or to kickstart your own business.

The Lodge is a member login only area with access to video training on how to build websites from scratch using the best modern tools.

What now? I have some ideas for you.

Go explore CodePen!

As a front end designer and developer, you should have an account on CodePen so you can save your snippets, present your ideas, and engage with other front end folk. I'd encourage you to go PRO as well, to unlock the full power of CodePen.

Get the newsletter!

You should sign up for the CSS-Tricks newsletter. It's a clean copy of all the blog posts each week, combined together, right to your inbox. If email isn't your thing, there is an RSS feed, iTunes, and lots of other ways to subscribe.

Listen to ShopTalk!

Subscribe to The Lodge!

The Lodge is a members-only, ad-free video learning area here on CSS-Tricks. Just like the free screencasts, but organized into four large complete series. Membership is also the #1 best way to support CSS-Tricks.

We can do the real footer now.

Site Links

Colophon

CSS-Tricks* is created, written by, and maintained by Chris Coyier. It is built on WordPress, hosted by Media Temple, and the assets are served by MaxCDN. The fonts are Source Sans and Source Code Pro. It is made possible by viewers like you who subscribe to The Lodge and through advertising for products and services I like.