Fun with Sass & font icons
⚠️ This article is outdated: CSS in JS is far superior to Sass, and font icons are no longer a good idea. Use inline SVG React components and in certain situations plain old .svg
files in img
tags.
Icon fonts have been been best-practice for a while now. They allow us to use tons of fully styleable cross-browser vector icons with one lightweight HTTP request.
Folk typically use font icons via non-semantic presentational markup such as class="icon icon-happy-face"
that should be avoided. With a pinch of Sass you can add icons to elements purely via your stylesheet using easy to remember names, without polluting your markup. Yay!
Setup
1. Prepare the font
These are some of the better options:
- Font Custom to manage icons as SVGs and generate fonts locally.
- IcoMoon to pick from a wide range of icons and generate fonts online.
By far the most intelligent workflow is a clever Font Custom setup automating the font generation and all this Sass config, using IcoMoon as a handy source for SVG icons. This article however is agnostic to how you source your icon font.
2. Declare the font
You can use a boring old @font-face
declaration, but here I use the nifty Bourbon mixin:
@include font-face("Icons", "../fonts/icons/icons");
3. List the icons
Next specify your icon names along with their matching unicode in the font:
// Map icon names to font unicode characters
$icons: (
email: "\f000",
newspaper: "\f001",
info: "\f002",
people: "\f003",
arrow-left: "\f004",
arrow-right: "\f005"
);
Manually setting up the characters sucks, I know. That clever Font Custom setup I was talking about handles this 100%.
4. Add the mixin
Lastly include the multi-purpose icon Sass mixin for use throughout your styles:
// For adding font icons to elements using CSS pseudo-elements
// http://jaydenseric.com/blog/fun-with-sass-and-font-icons
@mixin icon($position: before, $icon: false, $styles: true) {
@if $position == both {
$position: "before, &:after";
}
// Either a :before or :after pseudo-element, or both, defaulting to :before
&:#{$position} {
@if $icon {
// A particular icon has been specified
content: "#{map-get($icons, $icon)}";
}
@if $styles {
// Supportive icon styles required
speak: none;
font-style: normal;
font-weight: normal;
font-family: "Icons";
}
// Include any extra rules supplied for the pseudo-element
@content;
}
}
Usage
The icon($position: before, $icon: false, $styles: true)
mixin can help you:
- Attach a particular icon to an element complete with all the required styles.
- Attach just the required icon styles to a bunch of elements, without setting a particular icon.
- Set or swap the icon on an element without applying required styles.
- Set custom styles for your icon pseudo-element.
Attach a complete icon
With the following element:
<a href="mailto:foo@bar.com">Email me</a>
Easily add an icon with all the required styles:
[href^="mailto"] {
@include icon(before, email);
}
Which compiles to the following CSS:
[href^="mailto"]:before {
content: "\f000";
speak: none;
font-style: normal;
font-weight: normal;
font-family: "Icons";
}
Customize icons
You can apply custom styles to an icon pseudo-element by using brackets on the mixin. With the previous example, adding a few styles to an icon is easy:
[href^="mailto"] {
@include icon(before, email) {
margin-right: 20px;
color: blue;
}
}
This compiles to the following CSS:
[href^="mailto"]:before {
content: "\f000";
speak: none;
font-style: normal;
font-weight: normal;
font-family: "Icons";
margin-right: 20px;
color: blue;
}
Icons before & after
This should not come up often. Here’s the convenient way to handle it:
<button class="expand">Expand Horizontally</button>
.expand {
@include icon(both) {
color: gray;
}
@include icon(before, arrow-left, false) {
margin-right: 10px;
}
@include icon(after, arrow-right, false) {
margin-left: 10px;
}
}
This compiles to the following CSS:
.expand:before,
.expand:after {
speak: none;
font-style: normal;
font-weight: normal;
font-family: "Icons";
color: gray:
}
.expand:before {
content: "\f004";
margin-right: 10px;
}
.expand:after {
content: "\f005";
margin-left: 10px;
}
Attach an icon series
With the following elements:
<nav>
<a href="/news">News</a>
<a href="/about">About us</a>
<a href="/contact">Get in touch</a>
</nav>
Add just the required styles to the all items, then specify each icon:
nav {
a {
@include icon;
}
[href*="news"] {
@include icon(before, newspaper, false);
}
[href*="about"] {
@include icon(before, info, false);
}
[href*="contact"] {
@include icon(before, people, false);
}
}
Which compiles to the following CSS:
nav a:before {
speak: none;
font-style: normal;
font-weight: normal;
font-family: "Icons";
}
nav [href*="news"]:before {
content: "\f001";
}
nag [href*="about"]:before {
content: "\f002";
}
nav [href*="contact"]:before {
content: "\f003";
}
Setup icon classnames
If after all the above you still want to use nasty class names to add icons to elements, you can now set them up automagically:
// Set the required styles on all icons
[class^="icon-"],
[class*=" icon-"] {
@include icon;
}
// Setup a class name for each icon
@each $name, $char in $icons {
.icon-#{$name} {
content: $char;
}
}
This will output:
[class^="icon-"]:before,
[class*=" icon-"]:before {
speak: none;
font-style: normal;
font-weight: normal;
font-family: "Icons";
}
.icon-email:before {
content: "\f000";
}
.icon-newspaper:before {
content: "\f001";
}
.icon-info:before {
content: "\f002";
}
.icon-people:before {
content: "\f003";
}
.icon-arrow-left:before {
content: "\f004";
}
.icon-arrow-right:before {
content: "\f005";
}