100% Responsive Typography System using a Modular Scale
Hola a todos! That’s “Hello everyone!” in Spanish 😁 (yeah, I’m originally from Medellín, Colombia).
I’ve always said: “With a solid typography system, you can get away with zero images on your website”.
And I’m going to show you how to do just that .
Approach
I am a designer, not a developer, so in this tutorial there is a combination of basic math and “it-feels-just-right”, not intricate explanations or convoluted formulas.
Explanations are laid-back and simple with a friendly tone, just like I would explain things to my 8-year old son … see? lol.
Alright, let’s Rock n’ Roll!
Current Problems When Dealing With Typography
To give some context to this tutorial, let’s list a few problems we currently face when dealing with typography on the web:
- Font sizes are too small or too big for screen.
- Create too many media queries only to control font sizes.
- Create too many
font-size
declarations to control font sizes. - Lose track of the parent-child relationship when declaring font sizes.
- Anything else you’d like to add?… hehe
Main Objective
To create a solid and 100% responsive typography system based on a Modular Scale, for users to have a pleasant reading experience at any viewport.
Ok, So What’s a Modular Scale?
To quote one of the web typography gurus (and one of the creators of the tool we’re going to use) Tim Brown[1]:
“A modular scale is a sequence of numbers that relate to one another in a meaningful way.”
In other words, a Modular Scale is made of numbers that in one way or another are related to each other.
When you get the chance, check out Tim’s article More Meaningful Typography[2] on A List Apart.
How to create a Modular Scale?
For this we’re going to head over to modularscale.com. DON’T FREAK OUT! Give me a minute to explain .
This awesome tool is actually very easy to use. On the left column we see a couple of fields called Bases and Ratio, and on the right several font sizes with the phrase ‘The quick brown fox jumps over the lazy dog’.
Let’s create our Modular Scale:
Step 1: Define your base font size
On the Bases section we’re going to define our base font size of our project. It’s very common to use 16px
as a base font size since most popular browsers use this value as their default.
Side note: Actually not all browsers set their default font size at 16px[3], so make sure to test.
I recommend you use relative units like em
rather than absolute units like pixels, albeit, you can use pixels if you wan to create a Modular Scale.
Using em
for font sizes is a common practice, and in this tutorial we are defining 16px
as our base font size. This means that 16px
= 1em
. So let’s go ahead type 1em
in the first Bases input field.
First Base value: base font size
Step 2: Define a second Base value
Click on the plus icon overlapping the first input field where we typed 1em
. That creates a second input field. In it you can type anything you want. But remember the definition of Modular Scale that says “…numbers that relate to one another in a meaningful way.”? Yep, gotta keep that in mind
We’re going to find a number that in some way is related to our project. For this second number I often use the maximum width of the main container of my website. Since I usually design for 1280px screen width, I’ve found that a container with a maximum width of 1140px works very well:
- It’s wide enough to design on.
- Paragraphs are not too long and not too short if they need to expand the maximum width of that container.
- And at 1280px-width screens there is enough white space on the sides for the website to “breathe”.
Let’s add 1140
to the second input field in the Bases section.
Side note: After typing 1140
, refresh the page and you’ll see that the application adds the unit em
. Actually, it adds the unit to any values we use. That’s Ok because we want all the values to have the same units so the math that happens behind the scenes works correctly.
Second Base value: max width of our main container.
Step 3: Define a third Base value
For the third meaningful Base number we can use 12
, that’s because it’s a common practice to use a 12 column grid for layout. If your grid uses 16 columns then yeah, use that number.
Remember what the purpose is of all the numbers we use…? “…numbers that relate to one another in a meaningful way.”
Side note: More than three Base values is certainly an option and it may or may not yield a better Modular Scale. I personally haven’t had the need to use four or more Base values. If you want to explore that option, go for it, but for the purpose of this tutorial we’re going to stick with three Base values.
Third Base value: layout grid columns (12 columns for a grid is a common practice).
Step 4: Define a Ratio
The Ratio is what brings all the Base values together in a meaningful scale. The Modular Scale tool offers a great list of musical ratios that have been established to help build meaningful harmonies in music. We can use any of the predefined Ratios, or we can come up with our own.
The Ratio that works best with our Base numbers is 1.3
.
Keep reading to understan why 1.3
is the best Ratio for this Modular Scale.
Ratio, brings all Base values together to create a meaningful scale.
The Smooth Curve of Our Modular Scale
After creating many modular scales with this tool, I’ve learned that the best Modular Scale is the one with the ‘smoothest curve’ providing us with enough contrast between font sizes.
If we leave the default Ratio of 1.5
, the curve of our Modular Scale looks like this:
Curve not very smooth if you ask me.
What we’re trying to accomplish is a smooth curve in the scale, so with our current Base values of 1em
, 1140em
and 12em
and a Ratio of 1.3
, our Modular Scale curve looks like this:
Now this is a smooth curve with enough contrast in the font sizes.
Nonetheless, the “smooth curve” idea is not something you have to abide by, at all. If you prefer a more pronounced curve with even more contrast in the font sizes, that is also totally valid.
Ok, here’s the link to the Modular Scale we just created:
https://www.modularscale.com/?1,1140,12&em&1.3
Side note: Notice that the Base values and Ratio are visible in the URL, pretty clever from Scott Kellum and Tim Brown, the developers of the app.
Create a Responsive Font
Let’s leave our Modular Scale on the back burner for moment and let’s start working with CSS… yeah, I know, this is the part most of you were waiting for, eh? lol.
Alright, let’s do this.
Create a base HTML
First we need to create some HTML, doh! lol. Let’s create headers from <h1>
to <h6>
and short paragraphs under each one. Let’s also wrapp all those headings and paragraphs in a container: <main class="main-ctnr">…</main>
.
I’m going to use CodePen for this (oooobviously! hahaha). Use this anonymous demo to get you started.
If you don’t want to open the demo, no worries, here’s what it looks like:
CodePen demo with base HTML.
Now that we have our base HTML in place, let’s add this CSS:
body {
font-size: calc(12px + 1vw);
}
Side note: Browsers that do not support the calc()
CSS function, will ignore the above declaration and fall back to their defult font size. I think you’re seeing the next phrase coming…: Make sure to test! .
That’s it! All hail the mighty power of the calc()
CSS function! lol.
We now have fully responsive font, go ahead and expand and contract the viewport of the demo, you can see how the font grows and shrinks. Cool, eh?
Responsive font. Notice how the font size changes/adapts to the different viewport widths.
The values used in the calc()
function are arbitrary, they’re just a starting point. However, the units used, the order and the operation (px + vw
) ARE REQUIRED for a responsive behavior.
Side note: Times New Roman drives me crazy, lol. Let’s change it to Arial/Helvetica at least. The rule for the body
should now read:
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + 1vw);
}
Changed the font to Arial from Times New Roman, saaaaweet!
Side note: We could have used the font
shorthand property. However, I prefer to leave the two declarations separated. This will help us later on when we 'wiggle' and play around with the font-size
values. We can do this while keeping the font-family
the same and it will be obvious which values work best.
Keep reading, you’ll see what mean in a bit .
Make the main container 1140px wide
To start pulling things together, we’re going to create a rule for the main container. We give it a width of 100%, a maximum width of 1140px and center it in the viewport:
.main-ctnr {
width: 100%; /* For small screens */
max-width: 1140px; /* For large screens */
margin: auto; /* Center the container in the viewport */
}
Side note: Many people like to use margin: 0 auto;
, which accomplishes the same thing as margin: auto;
. I’ve used the second notation my entire career to center the main container of a website without any side effects whatsoever. The less I type, the faster I move on to more relevant work. Use the notation you feel comfortable with, it’s all a matter of personal preference.
Side note 2: Technically speaking, the declaration width: 100%;
above isn’t required since block level elements are by default 100% wide. However, I included it for good measure to be 100% sure that the main container will always be as wide as the viewport if it gets below 1140px
. Seriously, no pun intended, haha.
Side note 3: We could have use an element selector like main {…}
for the above rule, but I decided to use a class slector .main-ctnr {…}
to minimize specificity issues[4] down the road.
Remember that we’re designing for a 1280px screen resolution, so 1140px is a good maximum width.
Ok, the entire CSS should look like this so far:
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + 1vw); /* Responsive base font size */
}
.main-ctnr {
width: 100%; /* For small screens */
max-width: 1140px; /* For large screens */
margin: auto; /* Center the container in the viewport */
}
Set the Viewport at 1140px wide
It’s very important to note that before we start working on any modifications to the font size values and bring in the Modular Scale, we should have our viewport width at the same width as the max-width
of our main container.
Boy, that reads pretty weird, hehe. I’ll explain:
Our main container has a max-width: 1140px
so we set our viewport to 1140px
. In other words, if your main container is 960px
then the viewport of your browser should be 960px
as well. Otherwise the font size will not be the proper sizes in either small screens or large ones.
By setting the browser viewport at the same width of the main container, we will end up with responsive font sizes that are the right size at both small and large screens.
In the CodePen demo go ahead and make the viewport 1140px
wide.
Timeout: Viewport Tooltip Arrrggghhh!
CodePen has a little tooltip that displays the width of the viewport, you can see it when you resize it:
CodePen’s viewport tooltip hiding, wwwwwwwwhy! lol. CodePen, I love you!
But there’s a small problem with CodePen’s viewport tooltip (it drives me nuts too, haha): It disappears when you stop resizing the viewport, so in order to remember our viewport size we actually have to resize it again to make the tooltip reappear. Then it goes away. Arggh! lol.
So, I have this script that a friend of mine helped me create, that adds a small box at the bottom left of your page with the dimensions of the viewport.
Just add this script in the JS editor of the demo:
/*
Script to display the viewport size when working on responsive stuff.
Adpted to vanilla JS by: Taylor Hunt - https://codepen.io/tigt/
*/
var el = document.createElement("output");
document.body.append(el);
Object.assign(el.style, {
position: "fixed",
bottom: 0,
left: 0,
background: "red",
color: "white",
padding: "5px",
fontSize: "11px",
opacity: 0.7
});
function updateOutput() {
var html = document.documentElement;
el.value = html.clientWidth + " × " + html.clientHeight;
}
window.addEventListener("resize", updateOutput);
updateOutput();
Notice the small red box at the bottom left of the viewport:
Adding the viewport script to the CodePen demo.
Wiggle Time!
Ok, we are now entering the world of design where things are more “visceral” than mathematical, where the “how you feel about it” is more important than “the numbers don’t lie!”.
What I mean by this is that we now need to tweak (a.k.a. “wiggle”) the font size values we declared earlier in the body
in order to match the 16px
size of our body font.
This process is totally visual 🤷.
Our initial font size declaration was font-size: calc(12px + 1vw)
. We can modify either value, you can experiment in the demo if you want. But I’m going to wiggle the 1vw
value to find out which number makes the body font size look as similar as possible to 16px
.
That value is: .35vw
.
So our font size declaration should now look like this: font-size: calc(12px + .35vw);
And the entire CSS now looks like this:
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + .35vw); /* Responsive base font size */
}
.main-ctnr {
width: 100%; /* For small screens */
max-width: 1140px; /* For large screens */
margin: auto; /* Center the container in the viewport */
}
Here’s how adding and then breaking the font-size: 16px;
declaration makes practically no difference (only 1px wider) in font size using font-size: calc(12px + .35vw);
:
Testing the new vw
value to determine that the font size is practically identical to 16px
.
Bring the Modular Scale Now Already!
Aight! Sheesh!, lol.
Based on the Modular Scale we created:
- The
h1
is going to be =1.912em
- The
h2
=1.616em
- The
h3
=1.471em
- The
h4
=1.3em
- The
h5
=1.243em
- And the
h6
=1.132em
Remember that our paragraphs are simulating 16px
font size via the font-size: calc(12px + .35vw);
declaration in the body
rule. Makes sense?
The complete CSS should now look like this:
/* Modular Scale located here: https://www.modularscale.com/?1,1140,12&em&1.3 */
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + .35vw); /* Responsive base font size */
}
.main-ctnr {
width: 100%; /* For small screens */
max-width: 1140px; /* For large screens */
margin: auto; /* Center the container in the viewport */
}
h1 { font-size: 1.912em; }
h2 { font-size: 1.616em; }
h3 { font-size: 1.471em; }
h4 { font-size: 1.3em; }
h5 { font-size: 1.243em; }
h6 { font-size: 1.132em; }
Side note: For good measure, I added a comment at the top showing the link to the Modular Scale we created in case we need to reference it again in the future.
This is how our newly created typography system scales at different viewport widths:
Fully responsive typography system using our Modular Scale
Now we have a full blown, high quality typography system in place based on a meaningful Modular Scale.
And that’s it!
…
…
…or is it?
There’s something critical missing here, something actually quite important in a typography system: a Vertical Rhythm.
Vertical Rhythm
Let me quote Mr. Chris Peak to explain what vertical rhythm is (if you don’t already know ):
Vertical Rhythm is simply when a body of text is aligned to evenly spaced horizontal lines (think of your lined paper from grade school), making it more cohesive and easier to read.
I STRONGLY recommend you read his 2012 article Vertical Rhythm In Typography[5]. Yeah, it’s still totally relevant today, nothing in that subject has changed since then. And by the way, he talks about the Modular Scale there as well .
Vertical Rhythm is accomplished by manipulating the line heights in the text. And well, go figure, there’s a line-height
CSS property after all specifically for this!
Let’s add a responsive Vertical Rhythm to our already robust typography system.
Creating a Responsive Vertical Rhythm
As I said in this comment[6], it’s a common practice in typography to declare your line height between 120% - 150% of your base font size.
For this tutorial I’m going to use 150% of my base font size which is 16px
. Yep, that’s an arbitrary decision, because as designer I “feel” that I prefer to work with too much white space than with too little, and tweak things until they feel right. Welcome to the world of design! Hahaha!
So: 16px + 150% = 24px
.
Now, we need to turn those 24px
into em
, so we do:
24px ÷ 16px = 1.5em
Then in our CSS we would add a line-height
property:
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + .35vw); /* Responsive base font size */
line-height: 1.5em;
}
Side note: It’s best practice to declare your line-height
with a unitless value[7]. The reason for that is because using a unitless value allows the line height to proportionally increase/decrease in relation to our base font size if the base font size were to change.
There’s a problem though?… Any ideas?… Yep, the line height is not responsive. Yet.
To make it responsive, we’re going to “wiggle” the vw
unit in the line-height
value until we can visually tell it matches 1.5em
.
We now set our viewport in the CodePen demo to 1140px
and add this declaration to the CSS: line-height: calc(12px + 1vw);
. The body
rule now looks like this:
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + .35vw); /* Responsive base font size */
line-height: calc(12px + 1vw); /* Responsive Vertical Rhythm */
}
After wiggling the 1vw
for a bit, we can see that a value of 1.05vw
exactly resembles 1.5em
.
Now the body
rule looks like this instead:
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + .35vw); /* Responsive base font size */
line-height: calc(12px + 1.05vw); /* Responsive Vertical Rhythm */
}
Our body copy (paragraphs) now have a responsive Vertical Rhythm, as you can see the line height incrases/decreases depending on the width of the viewport.
However, our headings have some issues, look:
Paragraphs have great responsive line height, but our headings are messed up!
Let’s fix those suckers! lol
Responsive Vertical Rhythm for the Headings
First, let’s fix the line height, as you can see the descenders are touching the characters below and overlapping with the ascenders. Yuck! haha .
After ‘wiggling’ the values for a couple of minutes on all headings, this is how the CSS looks now:
h1 {
font-size: 1.912em;
line-height: calc(18px + 1.8vw); /* Responsive Vertical Rhythm */
}
h2 {
font-size: 1.616em;
line-height: calc(18px + 1vw); /* Responsive Vertical Rhythm */
}
h3 {
font-size: 1.471em;
line-height: calc(18px + .7vw); /* Responsive Vertical Rhythm */
}
h4 { font-size: 1.3em; }
h5 { font-size: 1.243em; }
h6 { font-size: 1.132em; }
h4, h5, h6 {
line-height: calc(18px + .2vw); /* Responsive Vertical Rhythm */
}
Side note: Headings h4
,h5
andh6
share the same line-height
since at their smaller font size a line-height: calc(18px + .2vw);
appears to work just fine since the text is perfectly readable.
Check out the demo:
Voilà, headings look awesome with their responsive line height.
Responsive Top and Bottom Margins for the Headings
And to finalize our responsive Vertical Rhythm, let’s add responsive margins to the top and bottom of all the headings.
Since we now know that our responsive line-height
value for our base font size is calc(12px + 1.05vw)
, all we need to do is use that same value in a margin
short-hand property for all the headings, like this:
h1, h2, h3, h4, h5, h6 {
margin: calc(12px + 1.05vw) 0; /* Responsive margins */
}
The first value calc(12px + 1.05vw)
is for the top and bottom margins. The second value 0
is for the left and right margins.
What About Users That Resize Their Browser’s Default Font Size?
Covered. This responsive font method works beautifully in such situations as well, check it out:
Changing font size with responsive font system in place.
And we are done! Woooohooo! lol
This is how the entire, complete CSS looks like:
/* Modular Scale located here: https://www.modularscale.com/?1,1140,12&em&1.3 */
body {
font-family: Arial, Helvetica, sans-serif;
font-size: calc(12px + 0.35vw); /* Responsive base font size */
line-height: calc(12px + 1.05vw); /* Responsive Vertical Rhythm */
}
.main-ctnr {
width: 100%; /* For small screens */
max-width: 1140px; /* For large screens */
margin: auto; /* Center the container in the viewport */
}
h1 {
font-size: 1.912em;
line-height: calc(18px + 1.8vw); /* Responsive Vertical Rhythm */
}
h2 {
font-size: 1.616em;
line-height: calc(18px + 1vw); /* Responsive Vertical Rhythm */
}
h3 {
font-size: 1.471em;
line-height: calc(18px + 0.7vw); /* Responsive Vertical Rhythm */
}
h4 { font-size: 1.3em; }
h5 { font-size: 1.243em; }
h6 { font-size: 1.132em; }
h4, h5, h6 {
line-height: calc(18px + .2vw); /* Responsive Vertical Rhythm */
}
h1, h2, h3, h4, h5, h6 {
margin: calc(12px + 1.05vw) 0; /* Responsive margins */
}
This how the finished demo looks like:
100% Responsive Typography System using a Modular Scale 🤘
Check out the CodePen demo here!
100% Responsive Typography System using a Modular Scale
And that my friends, is how we create a 100% Responsive Typography System using a Modular Scale, yeah! 🤘.
Final Words
I hope you enjoyed this tutorial, but more important, that you found it helpful.
Please let me know in the comments any thoughts, suggestions, ideas or corrections that you may have, there’s always room for improvement.
If you found this tutorial valuable, don’t forget to Share it! 👉
Thanks for reading 🙏!
Nos vemos! (See ya!)
—
Ricardo.
PS. HUGE Thanks to my friends Allen May and Taylor Hunt for their help reviewing this article.
Great article! Thank you. I made only a small config change for my preference. I changed from having everything proportional to “1vw” to “min(1vh, 1vw)”. I just feel that for reading sometimes, it happens that my screen is wider than usual but It doesn’t mean I would like a bigger font size for reading.
Thank you for your note. Would you mind sharing your final code? :)
Here is the code I slightly modified based on yours (almost no change). By the way, I’m a newbie. But what I like about your approach is not using 1rem. I feel that 1rem doesn’t guarantee that it is the best font-size/scale for reading so I think we shouldn’t rely on 1rem. When I use 1rem I feel that the appearance of text content is not consistent across web browser/device. note that the - - seems to display as – here due to page setting.
:root {
–screen-unit: min(1vh, 1vw);
}
html {
font-family: ‘Roboto’, Helvetica, sans-serif;
font-size: calc(12px + 0.35var(–screen-unit));
/ Responsive base font size /
line-height: calc(12px + 1.05var(–screen-unit));
/* Responsive Vertical Rhythm */
}
.main-ctnr {
width: 100%;
/* For small screens /
max-width: 1140px;
/ For large screens /
margin: auto;
/ Center the container in the viewport */
}
h1 {
font-size: 1.912em;
line-height: calc(18px + 1.8var(–screen-unit));
/ Responsive Vertical Rhythm */
}
h2 {
font-size: 1.616em;
line-height: calc(18px + 1var(–screen-unit));
/ Responsive Vertical Rhythm */
}
h3 {
font-size: 1.471em;
line-height: calc(18px + 0.7var(–screen-unit));
/ Responsive Vertical Rhythm */
}
h4 {
font-size: 1.3em;
}
h5 {
font-size: 1.243em;
}
h6 {
font-size: 1.132em;
}
h4,
h5,
h6 {
line-height: calc(18px + .2var(–screen-unit));
/ Responsive Vertical Rhythm */
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: calc(12px + 1.05var(–screen-unit)) 0;
/ Responsive margins */
}
Unfortunately, your code didn’t work. It breaks the responsive text.
Here’s a demo with it: https://codepen.io/ricardozea/pen/eYMygZb/a1e3a781314984b30f13b68f14c284bf?editors=0100
Can you create a demo instead where we can see your solution working?
One thing that your code improved, though, was that I noticed my browser rendered the text way faster when adjusting the viewport within the CodePen demo.EDIT-- Actually, never mind about rendering the text faster. It was caused by the parts of the code that didn’t work, hence, the browser was no longer making any calculations, and that’s why it “felt” faster.
I found that the text was too small and tight on mobile devices, which may just be my personal preference.
I ended up adding a @media query for small screens and made the following adjustments:
Tablets will likely need some tweaking too.
Yep, that’s exactly the whole point with this article, to adapt it to one’s circumstances.
Additionally, two 👍🏽👍🏽 for the
hover: none
media feature.Thanks for sharing Jono!
It’s worth mentioning here that
hover:none
will also target iPads that may need some additional attention, especially in landscape orientation.This can be used for iPads:
That’s right.
However, I personally never target specific devices or resolutions. I prefer to target points where the content breaks, hence the term, breakpoints.
In addition, I’m not a fan of promoting Apple products either >_<
That topic would be a good “Part 2” for this post. How to address common devices, and device orientations, without targeting devices or device orientations.
Thanks for the article, Ricardo. How does it go across screens having different PPIs? I developed the UI for my screen but the font size is less in high PPI screens.
Hello Tibin, thanks for your question.
This is screen-density independent, hence the relative units used. There must be something in your code that’s causing the issue.
If you have a link to share to see the problem first hand, I can help troubleshoot.