Jessica Chan I write coding tutorials and practical tips for the beginner web developer on my blog,

The elements of responsive typography

10 min read 3064

Elements of responsive typography

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

More great articles from LogRocket:

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.


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. I also write coding tutorials on my blog,

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



Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web and mobile apps — .

Jessica Chan I write coding tutorials and practical tips for the beginner web developer on my blog,

5 Replies to “The elements of responsive typography”

  1. Arrived from a Panda email. Good post, good information and of interest to me. Following along and looking to make sense of the formula. For the most part, everything is well explained. But there’s a pivot and the flow between concepts breaks for me. You twice show the formula outside of the css calc context, once with logical concepts, and then again with pixels. When you then move to the css calc context, you note to switch to rem/vw, but mention nothing about the way other information in the equation changes, or else I’ve missed it. I don’t follow where the “… – 20rem” comes in, or the “90 – 20”. How are those numbers calculated from the formula that uses pixels? It might be clearer in one of the references you link to, but it was a break in the flow of information for me. Thanks for writing!

  2. @JessicaChan. Great article! I was struggling to find a comprehensive and practical article about responsive typography up until I stumbled upon your piece. You basically nailed it! I really appreciated how you spent some time defining the basic concepts involved, the practical implementations of some solutions, their pros and cons, and yes, thank you for clarifying that mathematical formula. This article has been an insane relief for me and I feel more confident exploring other advanced concepts around this subject.
    I personally think I will implement some fluid typography with breakpoints: I like having more control and I feel the mixin you used in one of your example makes it bearable enough!

    One detail confuses me in the translation to rem, I don’t know if it is meant or if it’s a typo: isn’t 414px equal to 25.8rem, roughly?

    Thanks again for this article!

  3. I’ve been advocating proportional design with our team for quite some time. We’ve run into the issue of accessibility in particular when users zoom instead of adjusting browser settings. I looking into doing something like this instead of setting root to 5vw or some other value. Thanks.

  4. Very good post! Thanks for that. In fact, I don’t see the point using the third approach. If in the end again you need to add medai-queries to really put the limits… Why blowing the formular? The css-code in the second approach looks much easier to me (and propably to any thrid party reading the code) and does exactly the same thing.

  5. As a designer, it was so helpful for me. Font is most important in any type of design and I am learn about fonts from last three year. Thanks for sharing this.

Leave a Reply