Fluid Typography

Avatar of Geoff Graham
Geoff Graham on (Updated on )

Getting right to the code, here’s a working implementation:

html {
  font-size: 16px;
}
@media screen and (min-width: 320px) {
  html {
    font-size: calc(16px + 6 * ((100vw - 320px) / 680));
  }
}
@media screen and (min-width: 1000px) {
  html {
    font-size: 22px;
  }
}

It’s worth looking at our more recent post Simplified Fluid Typography for practical, clamped, viewport-based type sizing.

That would scale font-size from a minimum of 16px (at a 320px viewport) to a maximum of 22px (at a 1000px viewport). Here’s a demo of that, but as a Sass @mixin (which we’ll cover later).

See the Pen Base Example of Fluid Type w Sass by Chris Coyier (@chriscoyier) on CodePen.

Sass was used just to make that output a little easier to generate, and the fact that there is a smide of math involved. Let’s take a look.

Using viewport units and calc(), we can have font-size (and other properties) adjust their size based on the size of the screen. So rather than always being the same size, or jumping from one size to the next at media queries, the size can be fluid.

Here’s the math, credit Mike Riethmuller:

body {
  font-size: calc([minimum size] + ([maximum size] - [minimum size]) * ((100vw - [minimum viewport width]) / ([maximum viewport width] - [minimum viewport width])));
}

The reason that math is a little complicated is that we’re trying to avoid type ever getting smaller than our minimum or bigger than our maximum, which is very easy to do with viewport units.

For example, if we want the our font size in a range where 14px is the minimum size at the smallest viewport width of 300px and where 26px is the maximum size at the largest viewport width of 1600px, then our equation looks like this:

body {
  font-size: calc(14px + (26 - 14) * ((100vw - 300px) / (1600 - 300)));
}
The text changes fluidly to the width of the viewport

See the Pen JEVevK by CSS-Tricks (@css-tricks) on CodePen.

To lock in those minimum and maximum sizes, using this math within media queries helps. Here’s some Sass to help out…

In Sass

You could make a (pretty robust) mixin, like this:

@function strip-unit($value) {
  @return $value / ($value * 0 + 1);
}

@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
  $u1: unit($min-vw);
  $u2: unit($max-vw);
  $u3: unit($min-font-size);
  $u4: unit($max-font-size);

  @if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
    & {
      font-size: $min-font-size;
      @media screen and (min-width: $min-vw) {
        font-size: calc(#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} * ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)}));
      }
      @media screen and (min-width: $max-vw) {
        font-size: $max-font-size;
      }
    }
  }
}

Which you use like this:

$min_width: 320px;
$max_width: 1200px;
$min_font: 16px;
$max_font: 24px;

html {
  @include fluid-type($min_width, $max_width, $min_font, $max_font);
}

Here’s another of Mike’s examples, getting fluid rhythm just right:

Extending the Idea to Headers with Modular Scale

Modular scale, meaning the more space available, the more dramatic the different in size is. Perhaps at the largest viewport with, each header up the hierarchy is 1.4x bigger than the next, but at the smallest, only 1.05x.

See view:

With our Sass mixin, that looks like:

$mod_1: 1.2; // mobile
$mod_2: 1.5; // desktop

h1 {  
  font-size: $mod_1*$mod_1*$mod_1*$mod_1 *1rem; 
  @include fluid-type($min_width, $max_width, $mod_1*$mod_1*$mod_1 *$min_font, $mod_2*$mod_2*$mod_2 *$min_font);
}
h2 {  
  font-size: $mod_1*$mod_1*$mod_1 *1rem; 
  @include fluid-type($min_width, $max_width, $mod_1*$mod_1*$mod_1 *$min_font, $mod_2*$mod_2*$mod_2 *$min_font);
}
h3 { 
  font-size: $mod_1*$mod_1 *1rem;
  @include fluid-type($min_width, $max_width, $mod_1*$mod_1 *$min_font, $mod_2*$mod_2 *$min_font);
}

Other Reading