DEV Community

Cover image for The elements of responsive typography
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

The elements of responsive typography

Written by Jessica Chan✏️

Coding responsive typography can be quite confusing. What units are you supposed to use for font-size: px, em, rem, vw? How can you make sure your fonts are accessible? And what is fluid typography all about?

This article will walk you through the basics of sizing your fonts in CSS and look at a few different approaches you can use in your own website projects.

What are some best practices for writing font styles in CSS?

Let’s address some basic guidelines that will make your fonts look good on all devices and ensure they’re accessible for users who either need to zoom in or change their browser’s base font size.

Use relative units — not absolute — for fonts

Absolute units are static and their resulting sizes are not dependent on anything else. In CSS, the default unit we might turn to is the pixel (px) unit.

A font sized at 18px will be 18 pixels, no matter what device or screen you’re viewing it on. And pixels are familiar and pretty easy to understand. We know that 10px type is tiny, and 100px type is a giant headline.

It might seem tempting to use pixels for font-size, but it’s not recommended for accessibility reasons.

This is because even though zooming in will make pixel sizes greater, changing the base font size in the browser settings will have no effect. So pixels are not a fully accessible solution for fonts.

Relative units are measurement units that are dependent on other factors, such as the viewport width, viewport height, or a parent element.

There are several relative units: percents (%), em, rem, viewport width (vw), and viewport height (vh).

Percentage (%) units

Percentage units are simply a percentage of the parent element. If you have a <div> that is set to width: 50%, it will be half the width of its parent.

For fonts, setting font-size: 100% will make your text 100% of the base font size set in the browser. Most browsers default to a 16px base font size, so 100% of that would be 16px, and 50% would be 8px.

It’s generally considered best practice for accessibility to set your base font-size to 100% on the <html> element, and use relative units to handle all other font-size rules in your CSS.

This allows users to change the base font size in their browser, and have control over the font sizing for webpages.

Em units

The em unit is a holdover from the days when typography involved actual metal type blocks and printing presses. According to Wikipedia, “one em was traditionally defined as the width of the capital M in the current typeface and point size.”

So the actual name “em” seems to derive from the phonetic pronunciation of the letter M.

Since 1 em unit is based on whatever the current font size is, this means that text sized at 1em won’t always be the same resulting pixel size on the screen.

How that translates into CSS is that any text that is sized using em units will be dependent on the font-size of its parent. For example, if you have set your <html> to have font-size: 100% (resulting in 16px), a child element with font-size: 2em will end up being 2 * 16 or 32px.

One downside of using em units is that you might encounter unexpected behavior if you happen to nest font sizes.

Consider if you have your base font set to 16px, a child element set to 1.5em (which ends up being 24px), and then a paragraph element as a child of that element set to 0.75em.

You might think the paragraph would be 0.75 * 16px or 12px. But that 0.75em will be relative to the paragraph’s parent set at 24px, so the resulting font size will actually be 0.75 * 24px or 18px.

Rem (root em) units

You can avoid this nesting problem by using the rem (root em) unit.

As the name implies, this will behave like the em unit in that it is relative. But instead of being relative to the element’s parent, a font sized using rem units will always be relative to the root element of the website, which usually means the <html> element.

If you set your root font size to 100% or 16px, you can be certain that any other fonts using rem units will be relative to that 16px.

You can see em and rem units in action in a Codepen demo I’ve created here.

Viewport units

Viewport units are relatively new to the scene, and they can add an extra level of responsiveness to CSS.

They include viewport width (vw) and viewport height (vh). What these units measure is your actual device width and height.

Let’s say you have an iPhone 8, with a device width of 375 pixels and height of 667 pixels.

For this iPhone, 100vw is 100% of the viewport width, which would be 375px, and 50vw is 50% of the viewport width, or 187.5px.

The exciting part of using viewport units in relation to font sizing is that they’re inherently responsive, as the viewport changes with different devices.

If you set text to be font-size: 4vw this translates to 4% of your viewport width.

On that iPhone it will result in a font-size of 15px. If you have a tablet that’s 768px wide, you’ll end up with a font-size of 30.72px, and on a desktop screen of 1440px wide your text will display at 58px.

You might have already identified a problem with the usage of viewport units for fonts.

Having some paragraph text that is nice and readable at 15px on mobile phones will quickly become way too large at almost 60px for desktop widths.

And there is another drawback to using viewport units for fonts. The fact that they are relative to the device size, not the base font-size,means that they will not be affected by changing the base font-size in the browser.

Attempting to make text bigger by zooming in on the browser window may not go as expected, since the viewport itself changes when you zoom in or out.

You can see for yourself how text sized with viewport units behaves in a Codepen demo here.

However, as we’ll see later on, we can still harness the inherently responsive viewport unit for font sizing in some fashion.

Different approaches to responsive typography

Now that we have a baseline for how font sizing works in CSS, let’s look at a few different approaches we have for creating responsive text for the web.

We’ll look at the traditional responsive approach, as well as two different fluid approaches. Any of these three are fine solutions to responsive typography, depending on your own preferences and design requirements.

Approach #1: Responsive typography

Responsive design is the practice of writing CSS styles in a way that displays the website correctly on all device widths.

Whether you’re using a mobile phone, tablet, or ultra-wide monitor, the website should look good and be readable, even if it doesn’t look exactly the same on all devices.

Using media queries to target different breakpoint widths and writing different sets of CSS at different breakpoints are at the core of responsive design.

For example, you could build a 2-column layout on desktop, and have it stack to a 1-column layout for tablet and mobile.

For typography, we can use media queries to make the font-size smaller on mobile devices, and bigger on desktop devices:

html {
  font-size: 100%;
}

h1 {
  font-size: 2.25rem; // 36px
}

@media (min-width: 700px){ 
  h1 { 
    font-size: 3.5rem; // 56px
  }
}

@media (min-width: 1000px){   
  h1 {
    font-size: 4.75rem; // 76px
  }
}

In this example, h1 text will be 2.25rem (36px) for devices under 700px wide, 3.5rem (56px) for devices between 700px and 1000px wide, and 4.75rem (76px)for devices greater than or equal to 1000px wide.

With this approach, we’re using a base font-size of 100%, and relative font size units with rems, so the website will be accessible. The user can increase the size of the displayed content by adjusting their browser font size setting, or zooming in.

This is the standard responsive approach, which is accessible. The standard responsive approach also makes it pretty easy to understand how the fonts will behave by looking at the CSS styles.

I’ve created a simple working demo of responsive typography here.

Potential issues

One problem that comes with using media queries for your CSS styles is that it’s not completely future-proof.

While the majority of current phones are at most 414px wide and tablets are at most 800px wide, we can safely assume that those device widths will increase as technology advances.

So our breakpoints will probably need to be updated at some point. (This isn’t so much of a practical issue as it is one of principles.)

It would be nice if we could write styles that more elegantly scale up as the device width increases — and if it could scale fluidly no matter the device width without being so dependent on media queries.

If the styles scaled fluidly, text would be slightly larger on larger phones compared to smaller phones and larger on desktop screens compared to small laptop screens.

Let’s see how this next approach tackles that problem.

Approach #2: Fluid typography

Fluid typography refers to a way of sizing your fonts so that they scale up as the viewport width increases. And they don’t necessarily require media queries!

The two main components of fluid typography are:

  • The calc() CSS function, which allows you to perform mathematical operations using a combination of different CSS units
  • Viewport units, specifically the viewport width (vw) unit

As we mentioned earlier, viewport units by themselves will quickly get out of hand in how much it will scale up and down.

But what if we combined vw units with rem units, using calc()? Using this function, we can create a rule like this:

h1 {
  font-size: calc(1.3rem + 3.6vw);
}

This calculated value will be at minimum 1.3rem or about 21px if the viewport was hypothetically zero (which wouldn’t happen in the real world). Then the 3.6vw value will add a rate of growth that we can control to be more gradual than just viewport units by themselves.

At a viewport width of 414px, 3.6vw will be 3.6% or about 15px. So the calculated font-size will be 21px + 15px, or about 36px. At a device width of 1440px, 3.6vw will end up being about 52px, so the font-size will be 21px + 52px or about 73px.

This combination of calc() and viewport units is a really powerful way to write fonts that size themselves to any scale. And we know this solution will be accessible by setting html { font-size: 100% } and using rem units as part of the calculation.

You can check out a working demo of fluid typography here.

And if you’re interested in further reading on this topic, Chris Coyier talked about some potential use cases for mixed unit formulas in CSS Tricks, and Mike Riethmuller applied this to calculating font-size.

Potential issues

As we mentioned earlier, there are some accessibility issues with viewport units since they are not affected by changing the browser base font size. Although rem units themselves are affected, this will be offset by the viewport units in the calculation.

What this means is that the larger you set the vw portion of the calc() function, the greater that offset will be. But you’ll generally be all right if you set the rem portion of the function to 1rem or greater.

Getting more specific control with media queries

In addition, there is a degree of approximation when using this calculation function for your font sizing. By its very nature, it will continue to scale the font-size up as screen sizes increase, and will scale down as the device width decreases.

This may be fine for most cases, but may not always be desirable. If you need to add specific limits on how small or large the font-size ends up being, you’ll unfortunately have to add back media queries. That way you can force the minimum and maximum font-size values for the smallest and largest breakpoints, and scale fluidly between those widths.

Here’s an example of limiting the font to be no less than 36px and no greater than 76px:

h1 {
    font-size: 2.25rem; // 36px
}

@media (min-width: 414px){
  h1 {
    font-size: calc(1.3rem + 3.6vw); // fluidly scale
    }
}

@media (min-width: 1440px){
  h1 {
    font-size: 4.75rem; // 76px
  }
}

If you choose this approach, you may have to play around with the rem and vw values to try to get close to 36px at 414px, and 76px at 1440px. Personally I like the approach without media queries, since it lets you have fluidly responsive font sizing with a single CSS rule.

If you really need to have an exact amount of control over the font sizing at specific breakpoints, this last approach will give you that. Let’s check it out!

Approach #3: Fluid typography with “locks”

This method is similar to the previous approach, using calc() to calculate the font-size. It adds some math to ensure that the font-size will be exactly what we want at both the minimum and maximum breakpoints.

The downside, as we shall soon see, is that this exactness requires a more complex mathematical formula than simple addition. We’ll walk through how this works, and I’ll leave it up to you to decide if it’s a viable option for your projects.

Our previous approach used calc(1.3rem + 3.6vw), which will scale up as the viewport width increases, and scale down as it decreases.

What we want to do now is make the resulting font-size be exactly 36px at a viewport width of 414px, and exactly 76px at a viewport width of 1440px.

The formula, using a range of viewport widths and font sizes

To accomplish this, we’ll use a mathematical formula first written about in 2012 (as far as I could tell) by Tim Brown in a blog post to calculate line-height. Mike Riethmuller and others then applied it to font-size.

Tim later published a follow-up explanation using a canal locks analogy in 2016.

Here is the formula in the font-size: calc() function that we’re using:

The parts of the formula: minimum font-size, the “viewport multiplier,” and the font-size difference.

Let’s walk through the three sections of the formula.

The far left section starts off with the minimum font-size. This ensures that the resulting font-size will be at least that value, if not more.

Then, ignoring the middle section for now, we can jump to the last section on the far right to see the difference between the maximum font-size and the minimum font-size.

If we add this difference to the minimum font-size, we will end up with the maximum desired font-size.

Now let’s look at the more complicated looking middle section, which uses viewport widths. This is multiplied by the font-size difference.

If this multiplier is equal to 0, the resulting formula will be equal to the minimum font-size. And if the multiplier is equal to 1, the resulting formula will be equal to the maximum font-size.

This might be easier to see when we plug in actual values for each part of the formula.

Let’s plug in values and assume the current viewport width will be equal to the minimum viewport width, 414px. (We’ll use pixels first to more easily parse the math, then at the very end we’ll convert to rems)

36 pixels

When we do the math, we end up with 36px + (0 * 40px) = 36px. This is exactly what we want — at a viewport width of 414px, we want the font to be 36px.

Now let’s assume the current viewport width will be equal to the maximum viewport width, 1440px:

After we do the math, we’ll end up with 36px + (1 * 40px) = 76px. Again, this is what we want — at the viewport width of 1440px, the font will be 76px.

Converting our pixels to rems now, the final font-size rule will look like this:

h1 {
  font-size: calc(2.25rem + (((100vw - 20rem) / (90 - 20))) * (4.75 - 2.25));
}

Note that we need to remove units from everywhere except the initial minimum font-size and the numerator of the viewport multiplier in order for the calc() function to work correctly.

Adding media queries to limit the minimum and maximum font-size

Since the formula will calculate the font-size between our breakpoints of 414px and 1440px, we need to use media queries to force our minimum and maximum font-size values below 414px and greater than 1440px.

This is similar to what we did in the previous approach, and it’ll look like this:

h1 {
    font-size: 2.25rem; // 36px
}

@media (min-width: 414px){
  h1 {
    font-size: calc(2.25rem + (((100vw - 20rem) / (90 - 20))) * (4.75 - 2.25)); // fluidly scale
    }
}

@media (min-width: 1440px){
  h1 {
    font-size: 4.75rem; // 76px
  }
}

You can also make this into a Sass mixin for convenience:

@mixin calc-font-size($min-vw, $max-vw, $min-font-size, $max-font-size){
    font-size: calc(#{$min-font-size}rem + ((100vw - #{$min-vw}rem) / (#{$max-vw} - #{$min-vw})) * (#{$max-font-size} - #{$min-font-size}));
}

h1 {
  font-size: 2.25rem;
}

@media (min-width: 414px){
  h1 {
    @include calc-font-size(25.875, 90, 2.25, 4.75);
  }

@media (min-width: 1440px){
  h1 {
    font-size: 4.75rem;
  }
}

You can check out a live demo of this fluid typography formula here.

This last fluid typography approach gives us the highest level of control over how the font-size scales up.

However, it’s quite possible that this may be more complex than need be. There’s a non-insignificant amount of time needed to fully understand the math involved, which may add to frustration for a developer seeing this CSS rule for the first time.

Conclusion

If I had to choose one approach for my own web development needs, I would probably choose the simpler fluid approach with no media queries.

In my opinion, it’s hard to beat being able to set your font-size with just a single line of CSS:

h1 {
  font-size: calc(1.3rem + 3.6vw);
}

Even though there are no explicit minimum or maximum limits on the font-size, as long as you lean more on the rems as opposed to the vw portion of the calc() statement, you won’t end up with font sizing that’s too small or too large.

I hope that this article has helped explain some possible ways to handle responsive and fluid typography.

Which approach did you like the best? Leave a comment with your thoughts below.


Editor's note: Seeing something wrong with this post? You can find the correct version here.

Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post The elements of responsive typography appeared first on LogRocket Blog.

Top comments (0)