Karl Oscar Weber

Fluid Type Scales with Utopia

Fluid Type Scales with Utopia

Responsive websites are really cool. Most websites are responsive now. It's just how we do things. But how we get to a responsive website isn't always so easy. We end up with results that are... inconsistent to say the least.

A few years ago some interesting fellows proposed a completely fluid type scale to help us adapt our letters to smaller and larger screen sizes. They called it Utopia.

The problem™ is that good typography has a sort of hierarchy, and a relationship to that hierarchy. Headings are LARGE, and subheadings are not so large, text is normal sized. On large screens the gap between these sizes are bigger than they ought to be on smaller webpages. Traditionally these changes are achieved through media queries:

h1 {
    font-size: 1.5rem;
    line-height: 1.1; 
    @media (min-width: 375px)  { font-size: 2rem; }
    @media (min-width: 640px)  { font-size: 2.5rem; }
    @media (min-width: 960px)  { font-size: 3rem; }
}
h2 {
    font-size: 1.25rem;
    line-height: 1.2;
    @media (min-width: 375px)  { font-size: 1.5rem; }
    @media (min-width: 640px)  { font-size: 1.75rem; }
    @media (min-width: 960px)  { font-size: 2rem; }
}
p {
    font-size: 0.875rem;
    line-height: 1.5;
    @media (min-width: 375px)  { font-size: 1rem; }
    @media (min-width: 640px)  { font-size: 1.125rem; }
    @media (min-width: 960px)  { font-size: 1.25rem; }
}

This is a contrived example, but shows that the ideal size for our text is different across screen sizes:

Font Type Scale showing type scaling from small to large.

Clever folks have found out that setting your type at different sizes is more pleasing if there is a consistent ratio between those sizes. Let's say, like a third smaller, or a third larger at each step. It might look something like this:

5 rows, each has a type size, a circle that matches the size, and some sample text that is the text size of that row. The circle size and sample text descend in size by a third in each row. Thsi shows visual hierarchy in type sizes.

We call this phenomenon a Type Scale.

Fluid type scales respond to the viewport size. Smaller screens have less space, the gap between sizes on the scale should be smaller than on our big screens. A smaller, mobile type scale might look like this:

A Mobile type scale consisting of 5 rows. Each row has a pixel size label, a circle that matches the pixel size, and some text that matches the pixel size. The rows are smaller as they descend.

As you can see the type scale looks different. It's smaller, and the change in sizing is smaller too. The type scale begins at 16px and increases by 20% at each scale. A less dramatic change than beginning at 20px and increasing by 33.3% each scale.

Executing A Fluid Type Scale in CSS

Thanks to CSS Variables we can execute fluid type scales:

/* @link https://utopia.fyi/type/calculator?c=320,16,1.2,1280,20,1.333,4,0,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */

:root {
  --fluid-min-width: 320;
  --fluid-max-width: 1280;

  --fluid-screen: 100vw;
  --fluid-bp: calc(
    (var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) /
      (var(--fluid-max-width) - var(--fluid-min-width))
  );
}

@media screen and (min-width: 1280px) {
  :root {
    --fluid-screen: calc(var(--fluid-max-width) * 1px);
  }
}

:root {
  --f-0-min: 16.00;
  --f-0-max: 20.00;
  --step-0: calc(
    ((var(--f-0-min) / 16) * 1rem) + (var(--f-0-max) - var(--f-0-min)) *
      var(--fluid-bp)
  );

  --f-1-min: 19.20;
  --f-1-max: 26.66;
  --step-1: calc(
    ((var(--f-1-min) / 16) * 1rem) + (var(--f-1-max) - var(--f-1-min)) *
      var(--fluid-bp)
  );

  --f-2-min: 23.04;
  --f-2-max: 35.54;
  --step-2: calc(
    ((var(--f-2-min) / 16) * 1rem) + (var(--f-2-max) - var(--f-2-min)) *
      var(--fluid-bp)
  );

  --f-3-min: 27.65;
  --f-3-max: 47.37;
  --step-3: calc(
    ((var(--f-3-min) / 16) * 1rem) + (var(--f-3-max) - var(--f-3-min)) *
      var(--fluid-bp)
  );

  --f-4-min: 33.18;
  --f-4-max: 63.15;
  --step-4: calc(
    ((var(--f-4-min) / 16) * 1rem) + (var(--f-4-max) - var(--f-4-min)) *
      var(--fluid-bp)
  );
}

Notice in the code above that we have the --fluid-min-width and --fluid-max-width set to 320 and 1280 respectively. These are our limits. Our fluid sizes will reach their smallest at the lowest limit, and their largest at the biggest limit. This also prevents our text from shrinking or growing without end. (The math is difficult to parse without a minor lesson in algebra, so let's not do that.)

You can achieve the same effect, although with a bit less precision, and precalculated, with clamp:

/* @link https://utopia.fyi/type/calculator?c=320,16,1.2,1280,20,1.333,4,0,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */

:root {
  --step-0: clamp(1.00rem, calc(0.92rem + 0.42vw), 1.25rem);
  --step-1: clamp(1.20rem, calc(1.04rem + 0.78vw), 1.67rem);
  --step-2: clamp(1.44rem, calc(1.18rem + 1.30vw), 2.22rem);
  --step-3: clamp(1.73rem, calc(1.32rem + 2.05vw), 2.96rem);
  --step-4: clamp(2.07rem, calc(1.45rem + 3.12vw), 3.95rem);
}

The above is much easier to parse, but the calc parts present a mystery. Using these type scales is thankfully easy:

body { font-size: 100%; }
h1 {
    font-size: var(--step-3);
    line-height: 1.1;
}
h2 {
    font-size: var(--step-2);
    line-height: 1.2;
}
p {
    font-size: var(--step-0);
    line-height: 1.5;
}

A Fluid type scale without media queries!

No perfect Sizes.

A key tenet of fluid type is that there are no perfect font sizes. Which is pretty rad when you think about it. What matters is the relationship of the type sizes to one another, and the intended maximum and minimum size. When you don't have to worry about perfect type sizes, you're allowed to think in more abstract, role based ways. Is this a Heading? or a SubHeading? Content text or a blockquote? Should it be large, or small. By excising pixel perfect requirements for our text we instead have text with perfect, or intended relationships. It's these relationships that we care about. Once again to add emphasis and hierarchy to the page. Implicit Harmony.

The best time to adopt a fluid type scale is during a redesign or new site buildout. A refactor is also a good time. Adopting fluid type will make documenting and managing a design system much easier. You can lean on the intended type sizes in your components, and quickly build relationships between your content and compnents.

If you've been hesitant to try fluid type before I encourage you to adopt it. It kicks ass and will make your websites look very Schway.