Typography is arguably the essential part of a website. When we think about the content of a web page, we think about words. In this article, we’ll take a look at how to set a typography system in CSS.
This article is part of a series on design systems inspired by our library of web components. The library relies on a solid system of CSS globals. So this is us sharing the things we’ve learned setting the global style of our library! ✌
Components | CodyHouse
A library of customizable HTML, CSS, JS web components for designers and developers.
🔥 📢 We’ve launched the Typography Editor! A visual tool to set the typography of your web project based on the CodyHouse Framework. You can use it for font pairing, to generate the type scale and set responsive rules.
For those of you who prefer to skip the article entirely and take a look at the final CSS file, you can check it out here.
Setting the Typography system using CSS Variables
Designing a typography system means making decisions about:
- The typeface (font-family) you want to use.
- Type (modular) scale.
- Responsiveness of the text (size unit and breakpoints).
- Spacing and vertical rhythm.
- Colors (theming).
Let’s start with point one and two. Picking the right typeface is probably one of the first steps when you create a design system. Let’s assume you’ve browsed hundreds of font families and found the one you love (for now!); in your global/_typography.scss file, you can set your font-families as variables.
The primary font, in this case, is the “most used” font; or the body font. The secondary font can be applied, for example, to a heading element. This approach is arbitrary, of course.
Along with the typeface, we’ve defined two variables: the — text-base-size and the — text-scale-ratio. The value of the text base size is 1em, while the — text-scale-ratio is used to generate the type scale.
When applied to the <body> (we’re assuming no font-size is applied to the <html> element), 1em equals to 16px in most modern browsers. Since our framework is mobile-first, we’re saying: on small devices, I want the body text size to be ~16px (the font-size of the body is equal to the — text-base-size).
And yes, after a long debate, we’ve opted for the em unit. The reasons behind this choice are explained later, in the Spacing section of the article.
The modular scale is a set of values obtained from a base value (in our case, 1em) and a ratio, or multiplier ( — text-scale-ratio). You can apply the scale to any measurable element (margin, paddings, etc.). Here’s how you create a type scale taking advantage of the CSS calc() function*:
*We could have simplified the code by replacing the parts where we repeat — text-scale-ratio with variables (e.g., — text-lg: calc(1em * var( — text-md));), but we had issues with how the postcss-css-variables gulp plugin compiles nested calc() function and CSS Variables.
The reason why a modular scale is useful when applied to anything in a design system is that it generates a harmonious set of values, as opposed to setting each value independently (maybe obtaining them from a .sketch file).
Note that in defining each text size variable we multiply 1em by the — text-scale-ratio. That 1em is not the —text-base-size value. You could set a — text-base-size different from 1em (while you shouldn’t change the 1m in the calc() function).
Since the em unit is a relative unit equal to the current font size, if we update the — text-base-size variable at a specific media query, we update the font-size of the body, and, as a result, all the text size variables. The whole typography is affected.
The paragraph element inherits the base font size, while we set a specific font size for each heading element. Besides, we create some utility classes in case, for example, we want to apply the — text-xxl font size to an element that is not an <h1>.
Why including the type scale in your CSS? In one word: control.
Say we want to increase the body font size at a specific media query; for example, we increase the — text-base-size to 1.25em past 1024px. The heading elements are all affected by the change (the 1em in the calc() function is no longer ~16px, but ~20px), therefore they all become bigger. Let’s suppose we feel like increasing the size of the <h1> element even more. How do we do that?
One option would be increasing the — text-base-size value, but it’s not ideal. The body text is big enough, I just want to target the <h1>. Here’s the advantage of storing the — text-scale-ratio in a variable. We can edit it and affect everything but the body text:
With this technique, you can manage the size of all your text elements by editing only two variables. Not just that, you can take advantage of the em unit and modify all margins, paddings, and spacing in general by editing the — text-base-size at a root level.
This method is not just a time-saver IMO, it’s a powerful approach to making text responsive. Both designers and developers can benefit from it. All we need is setting a demo-typography.html file with some text elements plus some components. The designer can jump in and tweak the typography directly in coding.
So…what’s the catch? When you use the Developer Tools to inspect the font size, you won’t see numeric values, but variable names; which don’t provide much info. But does it matter? Is it critical to know that a font-size is 28px as opposed to 29.4560px? Your call! 😉
Now that sizing is sorted out, we need to deal with spacing. What is the ideal margin-bottom of an <h1> element? What about paragraphs? One option to deal with spacing is setting another scale to manage the vertical rhythm.
This is how it works:
- Set a base value, or baseline -> e.g., 24px
- Generate a set of values that are multiples of the baseline
- Use these values to generate consistent spacing between elements
The advantage of this method is that it creates a harmonious vertical spacing, that makes the user feels “safe”. Read more about why vertical rhythm is a good practice in this great article by Zell Liew.
That said, we opted for something different! Since we use the em unit for the font-size, we should set spacing in px or rem values if we want to preserve vertical rhythm (otherwise spacing would be affected by the em unit of the font size). As a result, if we change the font-size of the <h1>, we may want to tweak the margin-bottom as well (e.g., set a media query and increase the margin-bottom to — space-xl); otherwise the margin could look too small now that the <h1> is bigger than before.
Even though setting a media query to tweak the margin is not a big deal, we decided to rely entirely on the elasticity of em units. If you set your margins in em, chances are you won’t need to edit them.
Just my 2 cents: we often adopt in web design concepts originated in print design (e.g., vertical rhythm). We can break these rules, for the sake of simplicity, if doing so does not affect the user experience.
Here’s an example of how to set a spacing system using em units:
I generally separate spacing (_spacing.scss) from typography (_typography.scss), but this is up to you.
The reason why margins of lists and paragraphs descend from the .text-container class is to separate blocks of text from all the other places where a paragraph or an unordered list can be used. Optionally, you can target the <article> element.
Editing text size and spacing on a component level
The system we’re exploring makes sense as long as all text elements change accordingly with the two main text variables ( — text-base-size and — text-scale-ratio). How do we edit text size on a component level?
Option 1 would be targeting the component:
This change will affect the whole component, in all media queries. It’s like saying: “I want all text elements of this component to be 120% of what they would normally be”.
If you want to target a specific element inside the component, you have the following options:
The first option allows you to set the size of the <h1> as multiple of the — text-base-size (in this example x3); probably not ideal, because the size of the <h1> is no longer affected by the — text-scale-ratio variable. The second option sets a new size based on the — text-base-size. Not a fan of this option either, because it generates a value that is most probably off the scale, and it’s not affected by the — text-scale-ratio.
Of the other two options, the last one is my favorite (reassigning the variable), because the new value obtained still belongs to the type scale.
Theming is part of a more complicated topic worth exploring in a different article. To complete our typography exploration though, let’s see how to set the primary colors*:
*I generally prefer to store all the color variables in a separate file (_colors.scss).
The style above can be used to set some basic rules. Chances are we need to create variations of these colors. Soon, we’ll be able to do so by updating the variable values. Unfortunately, updating a CSS Variable using a class is not supported in all modern browser (and gulp plugins). For the time being, we need to change the variable entirely!
Putting all together
Let’s put all the pieces together! This is how we’ve set typography so far in our framework. It’s not necessarily the final version. We will review everything before publishing the library on Github. Oh and any feedback is welcome! Let us hear what you think and what could be done better.
I hope you enjoyed the article! For more web design nuggets, follow us here on Medium or Twitter. 🙌