⚠️ 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 inimg
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!
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.
You can use a boring old @font-face
declaration, but here I use the nifty Bourbon mixin:
@include font-face("Icons", "../fonts/icons/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%.
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;
}
}
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.
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";
}
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;
}
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;
}
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";
}
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";
}