Issues

SVG Icons in Umbraco

With the release of Umbraco 8.8.0 came a new feature that I, along with a few other brilliant developers have been working on for around two years. The feature introduces a new directive into the Umbraco backoffice that will fetch an SVG file and render it out inline into the page. The history of the PR goes back further than two years though…

The Seed

Back in 2015 I was lucky enough to attend a conference in London put on by the folks at Made by Many called EpicFEL. Coming from a mainly frontend background, at that point I had only been using Umbraco for about 9 months. I came away from that conference with a plethora of new information and inspiration, but one of the talks that stood out the most for me was by a wonderful developer by the name of Seren Davies (you can find her on Twitter at @ninjanails! I know, how awesome is that handle!).

During Seren’s talk, she explains how as a developer with dyslexia, she often relies on browser extensions to change the font on the websites she uses to make it easier to read. One of the side effects of this was that often when she went to a website such as GitHub and switched the font, all of the icons that had some visual meaning, but no accompanying text would be shown like so.

As you can imagine, this isn't particularly useful. It gets worse when some browsers interpret different icon font values differently too. Throughout her talk she also goes through some of the other disadvantages of using icon fonts. You can watch her entire insightful talk aptly named “Death to Icon Fonts” at https://www.youtube.com/watch?v=9xXBYcWgCHA.

A few months before Seren did her talk at EpicFEL, she also presented it at the London Web Standard conference, at which there were (I’m assuming) some employees from GitHub. As a result, in February 2016 GitHub released the following article explaining how they had done away with the old icon font used for their Octicons, and replaced them all with SVG icons. You can read about the process they went through to do it at https://github.blog/2016-02-22-delivering-octicons-with-svg/

Seren’s talk all the way back in 2015 not only ignited my interest in accessibility, but would also be the reason to begin the PR that kicked off Umbraco journey away from icon fonts. Before I had ever made my first PR I had checked the backoffice and noticed that similar issues were present. As an example, this is how some people may see the backoffice when icon fonts are changes

The Umbraco backoffice without the ability to see icons

The Umbraco backoffice without the ability to see icons

As you can see, some parts that have accompanying text are still understandable, but not everything. In this example you’d have no way of telling which icon deleted a property and which one opened its settings. At the time though, it seems like a pretty huge undertaking, especially for someone who had no experience to open source.

The Research and the Icons

Before I could get started I needed to figure out the good approach to adding SVG icons to the umbraco backoffice. Over the years since Seren’s talk I always steered away from icon fonts. I had evaluated how GitHub approached the issue and also worked on a few projects where I had added similar functionality. For example in one project I needed to be able to render some SVG icons that had been added via the CMS directly into the DOM without using an image tag. The reason for this was to make it possible to change the colour of the icons without needing to upload multiple versions of the same icon, which is how it would need to be done using an image tag. This was all done in the backend to remove any reliance on client side code.

I felt that with a few tweaks, a similar approach could work for Umbraco. There are quite a few ways that SVGs can be used, but essentially these were the main reasons behind the approach I took:

  1. An svg image sprite wouldn’t work as it wouldn’t be able to change colours.
  2. An inline SVG sprite added to the top of the DOM would add far too much bloat; 600+ icons adds a fair amount of markup.
  3. Using individual inline SVG would allow for changing colours and animations.
  4. If required it would be possible to add descriptive copy within the SVGs.
  5. Using individual SVGs would make it much easier to add custom SVGs to the backoffice in the future.
  6. Using SVGs in general would allow for multicolour icons if required. (This was actually highlighted to me by someone else later on.)

We’re going to jump forward 2 years now to a weekend away in the Peak District as the fantastic unconference that is CodeCabin. At this point Seren’s talk was still in the back of my mind, and a few times I had brought it up in passing conversation with various people in the Umbraco community. Now with some PRs under my belt and better confidence in contributing to Umbraco, alongside being surrounded with some of the very best developers Umbraco has to offer, I decided that I was going to try and actually do something about the icon system used in Umbraco.

The first thing I had to do before I could even think about how I would implement the SVGs was to find a way to create them. Luckily there are plenty of online resources that can take an icon font and spit out a bunch of individual svg icons. I can’t remember which one I used; all I remember is that I wanted to have them all as separate icons. In total I ended up with over 600 icons!

Now that I had all of the SVGs, next I had to work out the best way to get them into the DOM. I knew I could adapt what I had done in the past, but I needed to address the following:

  • In my projects I had read the SVG file then placed it directly into the DOM via a Razor file. This wouldn’t work for the Umbraco backoffice due to its use of AngularJS.
  • In my projects, because I was the one in control of adding the SVG icons I could be confident that there wouldn’t be any malicious code in them. That would not be the case for the Umbraco backoffice, so I needed a way to sanitize them.
  • In my projects the icons were only used in a few places. In Umbraco’s case there were some areas where you would need to be able to see all the icons at once such as the icon picker. With 600+ icons this could be a challenge.

The Backend

The PR adds a UmbracoAuthorizedApiController called IconController with two new API endpoints which do the following:

GetIcon(name: string) 

E.g. /umbraco/UmbracoApi/Icon/GetIcon?iconName=icon-document

Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path.

GetAllIcons()

E.g. /umbraco/UmbracoApi/Icon/GetAllIcons

Gets a list of IconModels representing all svg icons found at the global icons path.

The IconModel itself looks like so:

 public class IconModel
{
    public string Name { get; set; } // The name of the icon, without the prefixed “icon-”
    public string SvgString { get; set; } // The SVG string contained within the file.
}

Under the hood what these do is look at the file path in the Umbraco folder that contains all of the individual svg icons. If it finds an SVG file with a matching name it will use System.IO.File.ReadAllText(pathToFile) to get the contents of the SVG file. This happens incredibly quickly. If you request all of the Icons, it essentially does the same as if you were requesting a single icon, but for all 600+ icons, and it only takes milliseconds.

As I mentioned before, I needed to make sure any malicious code was removed from any SVGs. To handle this I added the HtmlSanitizer library, which is a well maintained project that can remove unwanted code from markup. By default this doesn’t account for SVGs, but thankfully it’s incredibly easy to add your own checks. To make sure I covered everything for SVGs I went through all the documentation I could find around SVGs to create an allow list for all the attributes, properties and tags that an SVG should have, excluding script tags. This means that if you try and add a new svg file that has a script tag within it, it will be removed.

In regards to adding your own SVGs there is already work being done to make this super simple via the App_Plugins convention. You can see the progress of it at https://github.com/umbraco/Umbraco-CMS/pull/8884#pullrequestreview-515421384

The Frontend

The way the Umbraco backoffice uses icon fonts is a little bit inconsistent, but for the majority of instances, they look something like this

<i class=”icon icon-folder”></i>

In the CSS, the icon class would be used to define the icon font & base size, and the icon-<name> would be used to select the desired icon.

Because of that existing convention all of the icon files are prefixed with icon- to make it easier to implement with the existing icon system, whilst maintaining backwards compatibility. 

As the Umbraco backoffice uses the old icon fonts everywhere, the merge PR doesn’t update all of them in one go. My first attempt at the feature did just that and it was simply too big of a change to be reviewed. That’s why the PR that was actually merged only included the backend functionality and a new directive, along with a few icon font to SVG icon conversions.

<umb-icon>

The main feature that the PR brings to the frontend is the introduction of the umb-icon directive, which looks like so:

// The most basic umb-icon directive looks something like this
<umb-icon name="icon-profile"></umb-icon>

// You can pass any other attribute through to it as you would any other directive, such as...
<umb-icon name="icon-profile" class="icon-class" ng-show="model.show"></umb-icon>

This directive will make a request for the icon, and if it finds one it will render it out as an inline SVG. If it can’t find an SVG it will fallback to the icon-font version. It also has built-in caching alongside lazy load functionality.

For individual uses of the umb-icon directive, the request to the “GetIcon” endpoint will only be made when the icon is within 100px of being in view.

When the icon picker list is opened up we do a request to the “GetAllIcons” endpoint, which caches all of the icons locally. Then when the umb-icon directive is added to the DOM, it will only inject the markup when it comes into view and won’t make a request for each of the icons.

The implementation ensures that there is full legacy support for old icon fonts to ensure there isn't a breaking change. That being said, once the functionality to add your own SVG icons is added I highly recommend any people using icon fonts in their packages to upgrade to the new, more accessible and feature rich umb-icon.

Uses so far and the future

This pull request started out with the intention to improve the accessibility of the icons throughout the backoffice, and the end result has actually come with a fair few additional benefits beyond its original goal. 

It adds an accessibility first approach to icons, in a way that will make it easier to add your own. When the functionality to add your own icons via the App_Plugin section is complete, any additional icons you add will appear automatically in the icon picker list. If the icons share the same name as the default icons it will override them too meaning it will also be possible to override the entire default icon system should you want to.

As the PR only adds the core functionality there are loads of remaining areas throughout the backoffice that still need to be updated to remove the old icons, but it’s yet another step in the right direction in terms. Here’s that screenshot shown previously but with the introduction of the umb-icon directive.

SVG icons without accompanying text

SVG icons without accompanying text

The icons without any accompanying text to provide context are still visible, rather than displaying a square.

Whilst I started the work, the final result was the result of a lot of collaboration from many brilliant people. It’s been truly humbling to see this new feature take legs and be picked up so enthusiastically by other developers so quickly. In particular I’d like to thank the following people for all of their help: 

  • Bjarne Fyrstenborg
  • Søren Kottal
  • Jan Skovgaard
  • Matt Wise
  • Matt Brailsford
  • Niels Lyngsø
  • Sebastiaan Janssen

Myself and a number of other contributors including those above have a variety of ideas to progress this new feature further. For example we would like to try and get a service worker in place at some point to handle the caching and potential preloading of commonly used icons too. There’s also already talk of how metadata can be added to the icons to make localisation easier too: https://github.com/umbraco/Umbraco-CMS/issues/9122

If you’ve enjoyed that article and have any questions around the new umb-icon directive, please feel free to get in touch with me via twitter at https://twitter.com/MikeMasey or by email at mike.masey@yoyodesign.com

comments powered by Disqus