Icon fonts, HAML and Sass

by Dale Sande

Icon-fonts. They are pretty awesome, but much like managing Sprite files, there are issues that make them a real pain to manage. Sure there are full libraries out there that you can grab wholesale and rely on their documentation, but for optimization and performance reasons you do not want to load up a series of libraries just to use a few icons. Leveraging the power of HAML and Sass we can make this less painful and at the same time maintain a living style guide. Basically, winning all over the place.

IcoMoon is a fantastic resource that allows users to select specific icons from various libraries, as well as upload custom SVG art and download a customized font library. In the download package there is an HTML document that illustrates the library you just created, but for most professional applications this isn't going to work. Mainly because you will probably not use IcoMoon's code verbatim. Customizing the HTML and CSS per your use is very common.

This leaves us with the task of maintaining our own ico-font documentation. Being a strong believer in style guide driven design, my process of developing UIs that self document is essential. Especially when dealing with things like sprites or icon-fonts, good visual documentation is vital. If the developers on the team cannot quickly see what icons are available, then the feature is pretty useless.

The challenge: when applying the style guide driven method, you need to make your code reusable, scaleable and easy to edit. The bourdon of writing code simply to display in the style guide is typically a deal breaker with most developers. Using some HAML and Sass magic, we can make this happen.

Ico-font library HTML display in the style guide

Starting with the HTML to display the library, we would manually write something like the following example taking inspiration from IcoMoon's supplied documentation.

<article class="styleguide_example">
  <span aria-hidden="true" class="icon-strategy"></span>
  <span aria-hidden="true" class="icon-strategy_01"></span>
  <span aria-hidden="true" class="icon-strategy_02"></span>
  <span aria-hidden="true" class="icon-dev_01"></span>
  <span aria-hidden="true" class="icon-dev_02"></span>
</article>

This seems like a pretty simple list and something that is easy to manage, but there is a lot of repeated code in there and we as developers HATE repeated code. We have the technology to make this better.

Looking at the previous example, just about all the code is duplicated, except for the class names. icon-strategy, icon-strategy_01 and icon-strategy_02 for example. Let's take this and using a simple feature in HAML we can write code that will loop through a list of these names and then write out the HTML we need for the browser. In the following example, we will take those ico-font class names and place them in an array.

- icons = 'icon-strategy', 'icon-strategy_01', 'icon-strategy_02', 'icon-dev_01', 'icon-dev_02'

Using the .each looping function we can write a small chunk of code that will leverage the repeated portions of the HTML and take all this manual updating away from us.

- icons.each do |icon|
    %span{"aria-hidden" => "true", :class => icon}

What we end up with is three lines of HAML versus x# of HTML. Much easer to manage and infinitely scaleable. New icons? No problem, add the class name to the array and you are done.

Ico-font library CSS display in the style guide

Now that we made the markup for the style guide easy to manage, what about the CSS? In the case of ico-fonts, much like sprites, there are multiple lines of CSS that need to be written. Doing this manually, you would write something like the following example.

.icon-strategy_01:after {
  content: "\e002";
}
.icon-dev_03:after {
  content: "\e003";
}
.icon-dev_02:after {
  content: "\e004";
}
.icon-dev_01:after {
  content: "\e005";
}
.icon-design_03:after {
  content: "\e006";
}
.icon-design_02:after {
  content: "\e007";
}
.icon-design_01:after {
  content: "\e008";
}

Once again, to a developer this is extremely painful to look at and the concept of copying and pasting is simply evil. Here is where Sass comes to the rescue. With Sass we can apply a process much like we did with HAML where we can iterate on a list of variables that are feed through a loop of code that in turn processes this manual madness.

Using the previous example, let's pull out the variable and take advantage of the redundant code. In the following example we can replace the icon names and the PUA values with variables.

.$ICO-VAR:after {
  content: "$PUA-VAR";
}

Now that we have the variable pattern, we will apply the values to a list and apply them to the variable $icons.

$icons: icon-strategy icon-strategy_02 icon-strategy_01 icon-dev_03 icon-dev_02 icon-dev_01;

To put this list to use, we will need the @each rule in Sass to loop through the list. The @each rule takes two arguments, one for the variable and the other for the SassScript expression. The @each rule sets the variable to each item in the specified list as it loops through, then outputs the styles it contains using the value of variable.

In the following example we will create $icon as the variable and $icons as the list expression. The iterated value from $icons will redefine the variable of $icon. The value of $icon is then passed into the CSS rule w/pseudo class .#{$icon}:after. The use of interpolation is required when using SassScript variables in selectors.

@each $icon in $icons {
  .#{$icon}:after {  <--
    foo:bar;
  }
}

First part completed. We have code that will iterate through a list and produce lines of CSS with each name as the class. But the resulting code will be a ton of css classes all with the same declaration of foo:bar. Not very useful.

To make the second part of our code, for the declaration of content: "$VAR";, we need to add the values of $VAR to our list. Since there is a relationship between the name of the class and the PUA content, it is preferred to keep these values together.

Lists have multiple ways of delineating and concatenating values. You can use spaces, commas , and you can even delineate each value between parenthesis (). What's important to note is that by using a combination of these delineators we can then group values in our list.

In our example we have icon-strategy which has a PUA of \e000. Then we have icon-strategy_02 with a PUA of \e001. The following example illustrates how we can use a combination of parenthesis () and spaces to create our combined list of values. By placing icon-strategy and \e000 within parenthesis () we are creating a concatenated value. And by simply placing spaces between each concatenated value, we can add more values to the variable list of $icons. Also note that we are putting the PUA value in single quotes '' because we want our returned value to be in quotes content: "\e000";.

$icons: (icon-strategy '\e000') (icon-strategy_02 '\e002') (icon-strategy_02 '\e001');

Now that we have our list, let's put it to some good use. Here we introduce Sass' concept of the nth function. Simply put, you can use this feature to tell Sass which item within a list to use nth($LIST, val).

In the following example you will see that we updated .#{$icon}:after to use the nth function, .#{nth($icon, 1)}:after. We still require that the returned value is interpolated using the #{} function, but the returned value will now step through the list and take the first value of each concatenated set of values.

@each $icon in $icons {
  .#{nth($icon, 1)}:after {  <--
    foo:bar;
  }
}

For the next part, content: "$VAR";, we need to step through our list again, except this time getting the second value. We will make use of the nth function again, but interpolation is not needed as we want the quoted value to be returned.

@each $icon in $icons {
  .#{nth($icon, 1)}:after {
    content: nth($icon, 2);  <-- 
  }
}

And there you have it. When the time comes and we need to add/delete icons from our ico-font library the hardest part is going to IcoMoon.

One more thing

For our ico-fonts to display correctly we need to add a few more declarations to our CSS. For one, we need the font-family. We don't want to add this to our @each $icon in $icons rule as this will duplicate these declarations each time it loops through the list.

Our solution is pretty simple. The following example illustrates how we can create the ico-font-base CSS rule as a placeholder selector.

 %ico-font-base {
    font-family: 'ico-fonts';
    speak: none;
    font-style: normal;
    font-weight: normal;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
  }

Then we can extend that into the previous @each $icon in $icons as illustrated in the following example.

@each $icon in $icons {
  .#{nth($icon, 1)}:after {
    content: nth($icon, 2);
    @extend %ico-font-base;  <--
  }
}

When the Sass is processed into CSS, this rule will loop through the list as expected and also crate a concatenated list of selectors all using this rule.

SassMeister living gist

Seeing code is one thing, playing with code is another. Check out the SassMeister Gist and happy coding.

List of rants

If you like this post, check out more of my rants