Repeatable responsive typography using CSS Custom Properties

I'm working on a side project with some friends, and it was time to do some responsive design sizing of display-sized text. Usually when I want fluid type the very next thing I do is pull up Geoff Graham's excellent guide to fluid typography, which boils things down to a basic formula:

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

@media screen and (min-width: [maxiumum viewport width]) {
  body { font-size: [maximum size] }
  
}

@media screen and (max-width: [minimum viewport width]) {
  body { font-size: [minimum size] };
}

It looks pretty complex because the variable names are long, but it boils down to 4 numbers: the min/max font-size, and the min/max breakpoints for the viewport. Or rather, as the size of the window goes from X to Y, the size of the font should go from A to B.

Usually when I implement this, I wind up using CSS Custom Properties (variables) to make the font-size easier to re-use:

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

/* breakpoints... */

/* elsewhere in the stylesheet */

.some-random .deep-selector {
  font-size: var(--fluid-type);
}

The main difference being using the --fluid-type custom property, which makes the value available in a bunch of places! This way if I need to over-rely on keeping my ems and rems straight for sizing other page elements to match the type.

In my current project (nothing to share yet, sorry!) I realized I had a few different type scales I wanted to use for different bits of UI. The fluid type scale is linear, which means it's possible to map one linear scale to another using calc. I'm also trying out the fairly-new-fangled clamp() function so I don't have to use breakpoints. So, instead of defining the "global" type scale to match a specific scale on the page, I'm defining a general scale that can be adapted where needed:

:root {
  --fluid-text-min-width: 480;
  --fluid-text-max-width: 1024;
  --fluid-text-width-range: calc(var(--fluid-text-max-width) - var(--fluid-text-min-width));
  --fluid-text: clamp(0px, calc((100vw - var(--fluid-text-min-width) * 1px) / var(--fluid-text-width-range)), 1px);
}

This scale is not really usable on its own unless you want some super tiny text! But it's really nice to use as a basis:

.some .specific .place {
  /* range from 12px to 32px */
  font-size: calc(12px + 20 * var(--fluid-text));
}
.avatar {
  /* not just good for text! */
  /* from 32px to 64px */
  --size: calc(32px + 32 * var(--fluid-text));
  width: var(--size);
  height: var(--size);
}

I'm finding this pattern useful on this project, but there are stil plenty of warts! If you're using breakpoints instead of clamp() you'll have to maintain the breakpoint sizes in both the custom properties and the media queries (media queries don't seem to support custom properties yet). It's probably also a good idea to put comments in where you define font-size as it's not immediately clear from a glance what the range of sizes is.

Should you use this in your work? Maybe! Compatibility is there (expecially with the breakpointed version), but it really comes down to how your design system works or whether you're using pre-processors which might offer a better options. For my medium-size vanilla CSS work, it's pretty satisfying!

#css #design