How I responsified my website’s typographic scale

While coding my redesigned portfolio website I came across the need to make its typographic scale responsive and I wanted to share the way I did it. Probably this is just neophyte-excitement over a problem that was solved long back but still I want to write about it.

Requirements

At the outset of the project I had two requirements and both stem from wanting to simplify and automate things (aka laziness).

  1. Changing the body text size should change the size of every other text element.
    Solution: Use rem and em units for text elements.
  2. Size of the body text should be decided by the viewport.
    Solution: Use vw unit for the base font size.

The solutions seem to be no-brainer but after coding and trying out in the browser I found the body text decreased way too rapidly with screen size; it becomes too small to read even at laptop screen sizes. So, I kept the base font size constant at 18px below certain screen size (tablets) and based it on viewport above that screen size.

html {
    font-size: 18px;
}
@media only screen and (min-width: $desktop-min) {
    html {
        font-size: calc(100vw * 22 / 1920);
    }
}

Hurdle #1: Too small body text on smaller screens

The first part was good, 18px worked well at mobile and tablet sizes. But the viewport-based font size for laptop/desktop screen sizes was still too small. So, I had to think up a better solution to better control the font size variation between screen sizes.

I designed the visuals (in Photohop) desktop-first at 1920px and I know 22px worked best for body text. So, I know the font size should proportionately increase from 18px to 22px as the screen size increases from 1025px ($desktop-min, the Sass variable I used) to 1920px. Putting this into the calc() function:

@media only screen and (min-width: $desktop-min) {
    html {
        font-size: calc(18px + ((100vw - #{$desktop-min}) * (22 - 18) / (1920 - 1024)));
    }
}

The calc() function checks how much the device’s screen size varies from the base screen size of 1024px and adds the proportionate increase in font size to the base font size of 18px. It worked and I guess this step is a no-brainer too.

Hurdle #2: Responsive typographic scale

My reqirement #2 is now settled completely but not #1. I derived the headings, sub headings, supporting text, etc., from the body text using a ratio (1.165). Found the ratio was too big for smaller screens; the headings were too big and even smaller words were breaking up into multiple lines. So, the ratio should change according to the screen size, i.e., the ratio should be responsive.

I wanted the ratio to change fluidly. So, I tried CSS custom properties (CSS variables). I created a custom property to calculate the ratio based on the viewport and then used the ratio to calculate the size of headings and other elements.

:root {
    --ratio: calc(1.1 + ((100vw - 320) * (1.165 - 1.1) / (1920 - 320)));
}
h4 {
    font-size: calc(1rem * var(--ratio) * var(--ratio));
}
figcaption {
    font-size: calc(1rem / var(--ratio));
}

The problem is, it didn’t work. The calc() function requires --ratio to be unitless but here the --ratio calculated based on the viewport has a length unit. I don’t know how to strip of the unit. So, I resorted to Sass mixin. I grouped all the font-size declarations under a mixin named font-size, with the ratio as a variable. Then I passed in a different ratio value for each screen size range using media queries.

@mixin font-size($ratio) {
    h4 {
        font-size: calc(1rem * #{$ratio} * #{$ratio});
    }
    figcaption {
        font-size: calc(1rem / #{$ratio});
    }
}
@media only screen and (max-width: $mobile-max) {
    @include font-size(1.1);
}
@media only screen and (min-width: $tablet-min) and (max-width: $tablet-max) {
    @include font-size(1.12);
}
@media only screen and (min-width: 1024px) {
    @include font-size(1.13);
}
@media only screen and (min-width: 1280px) {
    @include font-size(1.14);
}
@media only screen and (min-width: 1440px) {
    @include font-size(1.165);
}

I would’ve loved a fluidly changing ratio but for now I’ll have to make do with this. This is how it looks:

Get notified of new posts

I write only when I really have something to say; when I do so I’ll send one email and that’s it; no spam, no bullshit.