The strength of Sass is the mixins and functions. Being able to automate many of the repetitive coding for CSS is both amazing in building and maintaining a clean and efficient code. I often find many developers creating complex systems for simple tasks, such as managing a font stack. This can be tedious to set up and employ. In this article, I will explain how I automate this system.
The font stack is one of those problems which are often solved by simple variables. In this instance, it makes a lot of sense and is easy enough to work with. But when you work with our (beloved) designers from Dogstudio, you can be sure of having to use lot of font variants. It quickly happens that I do not remember all the properties of each variants. And when I say "use lot of font variants", I mean at least 15 in most cases.
Instead of simply define variables, I will ceate a font stack map and a mixin to use the map easily.
$base-font-stack: (
// Sans-serif
helvetica: (
light: (
family: (Helvetica Neue, Helvetica, Arial, sans-serif),
weight: 200,
style: normal
),
light-italic: (
family: (Helvetica Neue, Helvetica, Arial, sans-serif),
weight: 200,
style: italic
),
regular: (
family: (Helvetica Neue, Helvetica, Arial, sans-serif),
weight: 400,
style: normal
),
regular-italic: (
family: (Helvetica Neue, Helvetica, Arial, sans-serif),
weight: 400,
style: italic
),
bold: (
family: (Helvetica Neue, Helvetica, Arial, sans-serif),
weight: 700,
style: normal
),
bold-italic: (
family: (Helvetica Neue, Helvetica, Arial, sans-serif),
weight: 700,
style: italic
),
),
// Serif
georgia: (
regular: (
family: (Georgia, Times, Times New Roman, serif),
weight: 400,
style: normal
),
regular-italic: (
family: (Georgia, Times, Times New Roman, serif),
weight: 400,
style: italic
),
)
);
To explain a bit of what is going on here, we have a Sass map (of maps of maps) called $base-font-stack. This is the only thing we will ever update to add, remove or edit a font variant. EVER. At the first depth we have maps named by the font group name (examples: helevtica, brandon, clarendon, ...). For the second depth we have maps containing the properties of each font group variants. This identifier should be unique among the group (examples: regular, bold, light-italic). Finaly, these settings maps have three keys: family, weight and style.
family: CSS font-family value. weight: CSS font-weight value. style: CSS font-style value.
And now the magic mixin:
@mixin font($group, $variant: regular, $properties: family weight style, $font-stack: $base-font-stack) {
$font-properties: map-deep-get($font-stack, $group, $variant);
@if $font-properties {
@each $property, $values in $font-properties {
@if contains($properties, $property) {
font-#{$property}: map-get($font-properties, $property);
}
}
}
}
Once called, the mixin will loop through $base-font-stack
until it finds a match for both group and variant (default: regular
), then it will output the font familly, weight and/or style.
h1 {
@include font(helvetica, bold);
}
h1 .caption {
@include font(helvetica, light-italic, weight style);
}
p {
@include font(helvetica, regular);
}
p i {
@include font(helvetica, regular-italic, style);
}
p b {
@include font(helvetica, bold, weight);
}
blockquote {
@include font(georgia);
}
Compiles to:
h1 {
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-weight: 700;
font-style: normal;
}
h1 .caption {
font-weight: 200;
font-style: italic;
}
p {
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-weight: 400;
font-style: normal;
}
p i {
font-style: italic;
}
p b {
font-weight: 700;
}
blockquote {
font-family: Georgia, Times, Times New Roman, serif;
font-weight: 400;
font-style: normal;
}
It's easy, Isn't it? With these simple lines of code you will spare lots of times.
First, I identifie all font variants used by our designers. It's easier if they provide you a styleguide.
Second, I convert the fonts for the web if we have the licenses for or I create kits depending on the service the fonts came from (TypeKit, Google Fonts, ...).
Third, I create the map by adding all the values for the family, weight and style properties.
Basicaly to use webfonts you have to declare several @font-face
. But during the development you often try out multiple fonts variants, and in the end you don't really remember which one you're actually using. The result: imported fonts that take up bandwidth and HTTP-requests, but aren't used. So! What if our smart system automaticaly declare the @font-face
for all used webfonts only?
First, we add a optional font-face
property to our font variant map.
$base-font-stack: (
...
avenir: (
light: (
family: (Avenir, sans-serif),
weight: 300,
style: normal,
font-face: (
family: 'Avenir',
path: 'Avenir/Avenir-Light',
formats: (eot woff ttf svg)
)
)
)
);
And in our font
mixin we call a new track-fonts
mixin which aims to list the used font variants and uniquely store them in an array.
$used-fonts: ();
@mixin track-fonts($group, $variant) {
@if map-has-key($used-fonts, $group) == false {
$used-fonts: map-merge($used-fonts, ($group: ())) !global;
}
$font-map: map-get($used-fonts, $group);
@if index($font-map, $variant) == null {
$variations: append($font-map, $variant);
$used-fonts: map-merge($used-fonts, ($group: $variations)) !global;
}
}
Now that we have stored the fonts we can call at the very end of or code @include import-fonts();
to add the needed @font-face
rules.
@mixin import-fonts($font-stack: $base-font-stack) {
@each $group, $variations in $used-fonts {
@each $variant in $variations {
$font-properties: map-deep-get($font-stack, $group, $variant);
@if $font-properties {
// If we have a font-face key we create the font-face rule
$font-face: map-get($font-properties, font-face);
@if $font-face {
$font-family: map-get($font-face, family);
$file-path: map-get($font-face, path);
$file-formats: map-get($font-face, formats);
$font-weight: map-get($font-properties, weight);
$font-style: map-get($font-properties, style);
@if $file-formats {
@include font-face($font-family, $file-path, $font-weight, $font-style, $file-formats);
} @else {
@include font-face($font-family, $file-path, $font-weight, $font-style);
}
}
}
}
}
}
Check this gist for the complete and working code https://gist.github.com/fabricelejeune/bcdd3d4725d4e4cea672
Here's how you win with some mixins lot of time in your workflow. It's been several months since we work with this system at Dogstudio and all developers are happy. I hope you will be too.
i would like to add an error handling snippet to detect the compile/page load time on this - my concern is that im going to attempt to use this with typekit and the page load time