- March 19, 2015
Structured Typography with Sass Maps
For each development project at Happy Cog, we start with a set of starter files. Much like HTML5 Boilerplate or other similar initiatives, it’s intended to get us going as quick as possible. In that spirit, I’ve been thinking about how to move the needle in that direction as far as possible. The trick is to do so without burdening development with too many constraints and limitations.
One area that has had my attention is Typography. Type influences development nearly as much as design. Having a flexible but consistent approach to typography ought to provide a solid base to start development. If it’s predictable and consistent, it’s easier to change later in the project. It should also be easy to set up. The less code a developer needs to write to get to a working solution the better. With all that in mind, and the recent additions of maps to Sass, I set about creating a quick-start implementation for Type.
Starting with Data
My goal was to introduce a configuration file in Sass that would make setting typography as simple as possible. I started with declaring three sass maps that would allow us to set our fonts, leading (line-height), and sizes in one place.
For our fonts, I set up a map called $font-stack
where we can set fonts in a single place and call upon them later.
$font-stack: (
primary: #{'Helvetica Neue', Helvetica, Arial, sans-serif},
secondary: #{Georgia, 'Times New Roman', sans-serif},
code: #{'source code pro', monospace}
);
For leading, I set a map called $line-height
. Different values can be input for several different type scales. In this example there are three: “mobile”, “tablet”, “desktop”. I’m using this phrases as a shorthand for scale rather than any real relation to the type of device that it will render on.
$lh: (
mobile: 20,
tablet: 22,
desktop: 24
);
The most complex map is $type-sizes
. The size of each unique font-size, line-height, and margin configuration is set in this map. It also sets these values according to the “mobile”, “tablet” and “desktop” sizes seen in the $lh map.
$type-sizes: (
alpha: (
mobile: (
font-size: 42.725,
line-height: lh(mobile)*2.25,
margin-top: lh(mobile)*0,
margin-bottom: lh(mobile)*0.5
),
tablet: (
font-size: 48.829,
line-height: lh(tablet)*2.25,
margin-top: lh(tablet)*0,
margin-bottom: lh(tablet)
),
desktop: (
font-size: 54.931,
line-height: lh(desktop)*2.25,
margin-top: lh(desktop)*0,
margin-bottom: lh(desktop)
)
),
beta: (
mobile: (
font-size: 34.18,
line-height: lh(mobile)*1.75,
margin-top: lh(mobile)*1.5,
margin-bottom: lh(mobile)*0.5
),
tablet: (
font-size: 39.063,
line-height: lh(tablet)*1.75,
margin-top: lh(tablet)*1.5,
margin-bottom: lh(tablet)*0.5
),
desktop: (
font-size: 43.945,
line-height: lh(desktop)*1.75,
margin-top: lh(desktop)*1.5,
margin-bottom: lh(desktop)*0.5
)
),
gamma: (
mobile: (
font-size: 27.344,
line-height: lh(mobile)*1.75,
margin-top: lh(mobile)*1.5,
margin-bottom: lh(mobile)*0.5
),
tablet: (
font-size: 31.25,
line-height: lh(tablet)*1.5,
margin-top: lh(tablet)*1.5,
margin-bottom: lh(tablet)*0.5
),
desktop: (
font-size: 35.156,
line-height: lh(desktop)*1.5,
margin-top: lh(desktop)*1.5,
margin-bottom: lh(desktop)*0.5
)
),
);
The map itself goes a couple levels deep, indicating values for “Alpha”, “Beta”, and “Gamma” sizes. We’re using these names to avoid attaching these sizes to HTML elements like h1
, h2
, and h3
. A semantically accurate h1
may sometimes need to look like an h3
, so we try to decouple this association in that way.
It also sets line-height and margins in relation to the $lh
map, brought in from the custom lh()
function. Changing that value through a multiplier keeps margins in sync with the baseline rhythm as much as possible.
To access data from each map, we could use the map-get()
function in Sass, but that becomes unwieldy rather quickly. Borrowing a helper function from Hugo Giraudel’s article on Sass maps, I created helper functions to get that data. font()
is for getting the font stack value and lh()
for getting the line-height value.
For the $type-sizes
I turned to Sassy Maps and the map-deep-get()
function. With all that set, we can write some CSS that accepts these values and spits out the styles we need.
Our essential values for our Typography are now configurable from a single, easy-to-update file. They’re not obstructed in Sass code, and almost anyone who can open Sublime Text would be able to edit them.
Separation of Concerns
It’s easy to start lumping all your styles together using these values. And it’s not a terrible way to go. That said, my aim is to make a set-it-and-forget it implementation. The CSS that utilizes these values should be rarely updated. We’ll also need to allow space for our typography styles to be expanded. With that in mind, I created three Sass partials related to typography:
_type-defaults.scss: This would be where basic global defaults are set. Sort of an extension to our usual use of Normalize.
_type-sizes.scss: Where all type sizes are set, abstracted away from other styles.
_type-styles.scss: Where we set our `h1
` to bold and blue, and our `h2
` to gray, bold, and uppercased. In practice, this is where the meat of the work would get done.
_type-defaults.scss
is pretty standard. Here we zero-out margins or set hyphens on paragraphs. Or we make super-duper-sure that the browser knows that we want strong to be bold and em to be italic.
In _type-sizes.scss
, each unique type-size gets it’s own placeholder class. font-size and line-height is set for each media query.
%alpha {
font-size: rem(map-get-deep($type-sizes, “alpha”, “mobile”, “font-size”));
line-height: map-get-deep($type-sizes, “alpha”, “mobile”, “line-height”)/map-get-deep($type-sizes, “alpha”, “mobile”, “font-size”);
@media screen and (min-width: 31.25em) {
font-size: rem(map-get-deep($type-sizes, “alpha”, “tablet”, “font-size”));
line-height: map-get-deep($type-sizes, “alpha”, “tablet”, “line-height”)/map-get-deep($type-sizes, “alpha”, “tablet”, “font-size”);
}
@media screen and (min-width: 59.375em) {
font-size: rem(map-get-deep($type-sizes, “alpha”, “desktop”, “font-size”));
line-height: map-get-deep($type-sizes, “alpha”, “desktop”, “line-height”)/map-get-deep($type-sizes, “alpha”, “desktop”, “font-size”);
}
}
The %alpha
placeholder class is not output by Sass if not extended elsewhere. At the end of all the placeholder classes, a basic outline is created for how those sizes should map out. For example, setting h1
through h3
might look like this:
h1 { @extend %alpha; }
h2 { @extend %beta; }
h3 { @extend %gamma; }
This divorces the type scale from the element itself. It provides a nice outline of how the typography is configured in a project. It’s easy to update, change or add to later. It’s also easy to read and parse for new developers on a project.
From there, _type-styles.scss
is a blank canvas to write styles as necessary. This is where we determine the other properties essential to creating a unique design: fonts, weight, letter-spacing, etc. This could be done in a similar fashion as above, where a particular style is created as a placeholder.
%type-style-heading {
font-family: font(primary);
font-weight: bold;
text-transform: uppercase;
}
h1 {
@extend %type-style-heading;
}
You can mix together different styles as well, putting them together like lego-blocks. Here is an example of using an “emphasize” and “uppercase” placeholder classes to style an h1
and h2
.
%type-style-emphasize {
font-weight: bold;
}
%type-style-uppercase {
text-transform: uppercase;
letter-spacing: 0.02em;
}
h1 {
@extend %type-style-emphasize;
@extend %type-style-uppercase;
}
h2 {
@extend %type-style-uppercase;
}
Working this way produces typography styles that are quick and easy to setup. They incorporate into different contexts with minimal duplication. It makes clear on a project how typography works, how styles for typography get applied, where updates can reliably be added, and in what manner. The faster we can get details like this out of the way with a robust and flexible typography architecture, the more quickly we can turn around a project to great effect.