When I started web development, we -as webmasters- structured our web page layouts with
table elements. It may seem very stupid now, but that looked like a brilliant idea when I learned HTML. So, if you want to show a disk image next to the “Save” label inside a button, it was very likely to see a code something like this:
<td width="16" align="left"><img src="save.gif" width="10" height="10"></td>
By having better CSS positioning support in browsers, we simplified HTML a lot and used CSS background with a gif image and some padding tweak for the same purpose.
background: url(save.gif) left center;
Since this disk image doesn’t add any value to the content and just for improving the visual representation of the button to make it easy to see and understand, we hide it from HTML and set it on CSS. Cleaning all of the decorative elements from HTML was very smart. Because;
- Internet was slow, and HTML is not cached in the browser.
- HTML was the data layer, and we all knew that a decorative icon should not be placed in HTML.
But there was a problem with that approach; we started to have many icons and making separate HTTP requests for all those GIF images was very time-consuming in HTTP/1. Some smart people thought that we could merge all icons to a single image and show icons in the correct place by clipping with background positioning. We put all of the icons we have to a single GIF(or PNG) image in a vertical canvas by putting some safe space between them. Then just adding the line below to our CSS saved the day.
background-position: 0 -768px;
But that couldn’t go long since relying on element sizes was not safe. Then some people found a hybrid solution.
background: url(icons.gif) 48px 125px;
It may annoy you (it should) that we put an empty element to the document just to place a background image there. But luckily some later, CSS made her move and added “content” property together with
after pseudo elements. By waiting some time for having browser support we reached previous simplicity in HTML.
background: url(icons.png) 48px 125px;
Then some smart people said, “since we can give text content in CSS and we have some icon fonts already, why not use font icons with CSS?” If designers are ok with using single color icons -and they were all happy enough with that- font icons were a brilliant solution. Because they are vectors, they are single files, file size is small, and it was even possible to do some minor customizations with CSS, like color or weight. So we bought this idea very quickly.
Even some people remembered the ligature feature of fonts and we managed to write icon names as CSS content values like:
Font icons had some rendering issues because of different approaches browsers were using to render fonts, but in terms of accessibility, there was no issue until that time. Because those icons were decorative things, and they were not mentioned in HTML. Also, CSS-generated content was not red by screen readers.
But probably because many developers misused CSS-generated content by adding actual content to the pages via CSS, screen readers started reading CSS-generated content too. And this caused an accessibility issue for showing decorative icons with font icons. So apparently set of misuses and wrong fixes made CSS content property a less useful tool.
To hide the font icons from screen readers, you need to add aria-hidden=“true” to the container element. But since we don’t have a container element just for the icon, this was not a solution for us. Then this returns us to the previous step again. You add an empty element just for being able to hide it from screen readers. If you check fontawesome accessibility guideline, you will see those kinds of examples. So our example returned to this:
<button><i class="save-icon" aria-hidden="true"></i>Save</button>
After that point, SVG came to the scene. SVG is a vector graphic format and can be used as an external image in CSS or directly embedded in HTML. SVG is better rendered on browsers compared to fonts, it has multicolor support, it can be animated, and even highly customized by CSS. Inlining SVG icons in HTML is getting more and more popular nowadays. Even "icon components" are generally part of every UI library. Because seeing that we are convicted to have a specific DOM element just for showing an icon, why not use an SVG directly inside HTML?
But, we had a rule, remember: Decorative images should not be placed in HTML. And actually, we don't have big arguments to leave that rule behind. Still keeping HTML for "data-only" makes sense. You may say "in React we don't use HTML anymore, we directly create DOM". But once SSR comes to the picture, that DOM becomes HTML in our response, so no difference again.
You may ask, why it's that much problem to include icons in HTML. We can just add
aria-hidden="true" to prevent screen-readers to read it. That's true but it's not just about screen-readers.
- External images and styles can be easily cached in the browser. Inlining an SVG in HTML makes it slower to download and it's not easy or reasonable to cache HTML in the browser most of the time.
- Decorative icons are part of our theme and should be easily changed regarding screen estate, dark/light mode, or a theme selection for the user. With CSS these are trivial. But if you make icons a part of your templates, you have so much unneeded complexity in your JS.
Inlining SVGs are actually very powerful if it's not decorative, and part of your document, like charts. So you can put a dynamic chart to your document with SVG and you can style it with CSS. That's great.
So, what is the ideal way to "not" embed SVGs in HTML for decorative icons? If we are talking about something decorative, actually CSS is (should) be the only solution. If you don’t need to style an SVG icon with CSS, using external SVG as CSS background is still a good option. Even though it’s possible to change color of an SVG background with CSS, it’s not a very easy-to-use solution for every use-case.
Another approach is fetching SVG file with JS within a component. This is the approach we used in our Baklava Web Component Library. In this approach -in an icon component- you are lazily fetching external SVG file and embedding it to HTML to take the advantage of styling SVG with CSS and not embedding them to our templates directly. This is hybrid solution though. We are still actually embedding SVG to latest HTML in use, but we do it lazily.
For me, decorative icons in web is still a non-fixed issue. I’m still looking for an ideal solution to put decorative visuals to our Web Designs without needing to disturb our HTML.