How I’m Dealing With Font Sizes

I have the hardest time with fonts. It’s so much which font to use, but how to size them appropriately for any given screen size. I might be particularly sensitive to this only because I often find myself bumping up text using with some ⌘ + action, or simply switching to Safari’s Reader Mode for a better reading experience. I often find that content is too small for me and I try to prevent that for old geezers like me, with degrading eyesight.

I’m thinking of context, not viewport widths

One way to think about font sizes is not only by device, but how that device is used. This isn’t prescriptive or anywhere near comprehensive, but what I mean is like:

  • Desktop/Laptop: Probably sitting (or standing) at a desk and viewing content on a large screen that’s approximately 24-36 inches from their face. Assuming good ergonomics are a virtue, the user is likely leaning back (unless they’re standing at the desk).
  • Tablet: Probably sitting, perhaps even legs up with the tablet resting on them. We’re looking at a distance that’s probably 14-20 inches from the face.
  • Phone: Let’s go with standing on this one and assume the user is standing straight (not hunched with neck down). I wouldn’t be surprised if the screen is being held somewhere between 10-16 inches from the face.

I think this way a lot because I’m constantly changing my natural posture to read content. If text is too small on my desktop, I find myself leaning forward to get a closer look. If text is too large on my phone, I find myself wishing I had Inspector Gadget-like powers to hold the screen as far away as possible. I have absolutely zero proof that this is the way you or other people feel reading content on the web, but it’s how I’ve been empathizing and what I’ve been using to base many of my font sizing decisions.

I’m bullish on clamp() for responsive sizing

Me and media queries have had a long love-hate relationship when it comes to font sizes. I love that I’m able to adjust the size when the viewport hits a certain threshold, but I hate that I find myself writing a crap ton of media queries to get fluid-like sizing.

Then came calc(). That allowed me to use a pattern that’s become popular for automatically scaling font sizes. The idea is you define a minimum font size on the <html> element, a maximum font size in a media query that’s set for a larger screen, then declare a font size in one more media query that sits between the base <html> element and the media query for large screens. This “middle” media query is where calc() — true to its name — calculates sizes between the two extremes.

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;
  }
}

That blew my freakin’ mind when I first saw it. But then clamp() became a thing. It does exactly what that snippet is doing, but with a grand total of zero media queries.

For example:

h1 {
  font-size: clamp(24px, calc(24px * 1vw), 36px);
}

See that? Now the font stays within a range of 24px and 26 pixels. The middle value is the “ideal” size and it’s set to a number that multiplied by a responsive unit — that allows the browser to calculate font sizes within the given range.

Any time I can reduce a snippet of code by ~75% is a good thing.

I’m making variables out of every value

Custom variables are The Thing™ that I was missing from CSS that kept me reaching for Sass. I still use Sass for things like nesting and partials, but write a lot more vanilla CSS in those files than I ever used to.

I definitely use them in my font sizing stack. I start with a base font font size:

--text-size-base: 18px;

So far, so good. Then I create more sizes based on that:

--text-size-base: 18px;

--text-size-tiny: calc(var(--text-size-base) - 6);
--text-size-small: calc(var(--text-size-base) - 4);
--text-size-medium: var(--text-size-base); /* Takes the base size */
--text-size-big: calc(var(--text-size-base) + 4);
--text-size-large: calc(var(--text-size-base) + 6);
--text-size-huge: calc(var(--text-size-base) + 12);

Still good? I like having these as utilities should a situation come up where I need a specific font size but want to keep it consistent with everything else.

I’m using a variable for scaling

The thing about fluid typography with clamp() — besides lacking full browser support — is that the ideal (or middle) value needs to be a responsive unit. That way, the computed value changes depending on the container that contains the text, and we clamp it so that value never goes above or below our range.

I like basing things on viewport units (vw) only because it truly responds to the size of the browser. It’s sorta arbitrary what exact vw value to use, but I might only think that because I suck at math. I grab a nice round number, say 10vw, and make that a variable I can use as a multiplier that converts the fixed pixel value for the ideal size into a responsive unit.

--text-size-scaler: 10vw;

I like 10vw because it’s the same as saying 10% of the browser width. That’s easier to calculate for math duds like me.

I’m clamping the variables

Now, instead of raw numbers on an element, like this:

h1 {
  font-size: clamp(24px, calc(24px * 1vw), 36px);
}

…I can drop my variables in there instead:

h1 {
  font-size: clamp(var(--text-size-large), calc(var(--text-size-base) * var(--text-size-scaler)), var(--text-size-huge));
}

Now, I have a few things working for me with this setup:

  • I can reuse these variables anytime I need them.
  • I can update the variables in one place.
  • I can override any of the variables on an element if I need to tweak something for a specific use case.
  • I can enjoy a nicely-scaled font size at any screen size.

Is this the best solution? Probably not. Truthfully, I normally raise an eyebrow when I see crazy-looking calculations with subjectively-named variables. They make sense to me, but I’d have to leave detailed comments or show someone else the ropes if I ever planned to hand this off. I do sorta miss the days when font-size was typically a single-value property. There was a lot less to grok back then.

Either way, the benefits of an improved developer experience and user experience outweighs nostalgia for “simpler” days.

This is not a font “stack”

Nope, nope, nope. All I’m doing here is showing how I’m tacking font sizes. If we were talking about a “font stack” in the traditional sense, we’d be referring to a series of fonts on the font-family (or font shorthand), like:

font-family: "Alfa Slab One", Helvetica, sans-serif;

But as web typography gets more and more advanced, “font stack” might also be used to refer to an overall strategy for legibility, including fonts, fallback fonts, font sizes, line heights, and other things contributing to the “stack.”

What do you do?

Seriously, what do you do with your font sizes? Everything I’ve shared here is merely tailored to my needs and I’d be surprised if anyone else finds it works perfectly for them. I wanna see what you do!

✏️ Handwritten by Geoff Graham on March 16, 2021(Updated on 3/25/2021)

28 Comments

  1. zakius
    # March 17, 2021

    I’d just go the easiest route and leave default size, this should work everywhere (except mobile chromium that makes text way too big) and respects user preferences easily (once again, except mobile chromium)

  2. kat
    # March 18, 2021

    Nice to see different ways of doing this. I have only used + and not * inside the calc. Will have to check that out to see if I can get rid of the various vw numbers I use.

    One tip: stop using px units. It overrides the user’s size settings. Font-sizing isn’t so much about screen sizes as it is about being able to meet user settings. If you provide a scale that is close to common sizes, you are very likely to hit the mark because users have adjusted their browser or system settings based on that.

    The calculation in the second argument makes more sense if you combine a user relative unit with a small viewport-relative unit to get that increase/decrease.

    • # March 18, 2021

      Ooo, good call. Relative is definitely the way to go.

  3. # April 4, 2021

    Thank you for the insight, it will help us a lot in our projects. could you please suggest some of the way how to reduce font load time :)

  4. # April 11, 2021

    That’s a really good stuff to deal with font sizes based on variables.

  5. # July 6, 2021

    Normally, a project will have a set of pre-determined font sizes, usually as variables named in such a way that seeks some semblance of order and consistency. Any project of considerable size can use something like that. There are always headings, paragraphs, lists, etc. You could set font sizes explicitly and directly everywhere (e.g. font-size: 18px). Raw CSS, as it were. I do see that occasionally — mixing not just sizes but also units like px, rem and em in mindless chaos.
    That’s why the CSS of a project typically uses variables or mixins — we’re shooting for structure, maintainability and, ultimately, consistency. We all know naming is hard and it doesn’t take looking much further than naming font size variables to see why. How should we name a small font size variable so it’s clear that it’s smaller than a large font size variable? And what happens if we need to insert a new variable in between them — is that one named in a way that clearly explains its relationship to the other size variables?
    We’ll continue talking about naming font size variables in this post. But, really, the issue extends beyond font sizes to any sort of size or length value. Think paddings, margins, widths, heights, border radii, etc. These things need structure and consistency, too!

    How do you define font sizes in your project? Does it look something like this with custom variables:
    :root {
    /* Font size variables */
    --small: 12px;
    --medium: 16px;
    --large: 24px;
    }

    Or perhaps in Sass (which is what we’ll be using throughout this article) you might have variables for $small, $medium, and $large font sizes.
    Fine. After a while, let’s say the designer adds a new <h1> heading for a hero section. And it is very large. Larger than anything you have in the project. No problem, you reply. You add an $xlarge to the project, and go about your day.
    The following day, the designer makes a nice form label, which again has a new font size. This new size, though, is larger than small, but smaller than medium.
    Here we go.
    What should you call it? $small-medium? $small-2? $smedium? Whatever you name it, you won’t be happy with it. Because there is no word for that.
    Or should you maybe refactor it? Create a new $xsmall, and change all instances of $small to $xsmall? And then you can use $small for the form label? There’s a small risk that you will forget to change somewhere and, hey, presto: a bug. What happens next time, when something is introduced that has a larger size than the $medium variable value? Do we have to refactor $large and $xlarge too?
    I suggest adhering to a scale, always. An easy fix would be to further abstraction, perhaps ditching numbers and sizes in favor of functional names, like $form-label instead of $small-2 or $xsmall.
    But imagine having a set of font sizes like this:
    $small: 12px;
    $form-label: 14px;
    $medium: 16px;
    $large: 24px;

    That is a broken scale. It’s a size concept and a component concept mixed together. It raises questions. Should an alert or a button be allowed to use $form-label? Yuck.
    Maybe you have a Greek thing going on, naming the variables $alpha, $beta, $gamma? Let me ask you then, what is then bigger than $alpha? $alpha-large? Or wait, is $alpha the small one?
    I have also seen names like $button-font-size, $label-font-size, $blockquote-font-size. That seems to me like one variable per element used, instead of a scale, and sounds like it could be a lot of duplicated code if the same value is being used in multiple places, but with different names.
    Perhaps you’re working with one base font size and percentages only? Sure, but I would say you need variables for the percentages. That’s how Geoff handles font sizing and even he admits that the setup raises his own eyebrows. Calculations with subjectively-named variables might be clear to you, but crazy-looking and complicated for anyone else jumping into the project.
    h1 {
    font-size: clamp(var(--text-size-large), calc(var(--text-size-base) * var(--text-size-scaler)), var(--text-size-huge));
    }

    We need a better system
    Adding and removing stuff constantly is the way we want to work. This is modern day development — MVP, Agile, and all the other hot buzzwords.
    What we need is a scale syntax that allows room for changes. Adding a new size to the scale should be easy without introducing breaking changes. I’m thinking of a kind of scale that is both flexible and infinite. It must be more sophisticated than $small, $medium and $large.
    It should be also be descriptive and intuitive. Preferably, you shouldn’t have to look up the variable names in the settings file or the config, or wherever you store these things. I don’t have the slightest clue if $epsilon comes before or after $sigma. Do you?
    Using existing systems
    Before trying to invent something new, is there an existing syntax or system we can leverage? Here are a few I’ve encountered.
    International system of units
    Surely, you’re familiar with terms like “kilobyte” and “megabyte.” Europeans are very used to “millimeter” and “centimeter.” Other examples are “giga,” “tera,” and “peta.” These prefixes can be used for length, weight, volume and more. Could a $centi font size work? It is intuitive to a certain extent, that is, if you’re familiar with the metric system. This is a finite scale. And there’s no room to add new sizes because they are already set.
    Traditional point-size names
    Long before computers and desktop publishing, books and newspapers were printed with lead type. The type setters had different names for different sizes. The sizes have a reference to a point size (pt) and could, in theory, be used for pixel sizes (px).
    The type sizes in this system are called “Nonpareil,” “Pica,” “Cicero,” and “Great Primer,” just to name a few. The names are different depending on continent and country. Plus, the same name can have different sizes, so… quite confusing.
    That said, I do like this system in a way because it would be like paying respect to an old craftsmanship from times past. But the names are so weird and specifically meant for type sizing, that it feels like a stretch to use for things like breakpoints and spacing.
    Placing everyday objects on a scale
    How about using stuff from our everyday life? Say chili peppers.
    There are many kinds of chili peppers. The $habanero, is hotter than the $cayenne, which is hotter than the $jalapeno. That would be fun, yeah?
    But as much as I enjoy the idea of writing font-size: $tabasco, I see two problems. If you’re not into peppers, you cannot know which pepper is hotter than another pepper — so, it’s not universally intuitive. Also, the bell pepper is 0 on the Scoville scale, and nothing is below that. Carolina Reaper is the hottest pepper in the world, so the scale is finite.
    And yeah, peppers scale-wise are not larger or smaller, they are hotter. Bad concept. Maybe something more common, like types of balls?
    There‘s a large range of different kinds of balls. You have handballs, soccer balls, volleyballs, etc. Need something larger than a medicine ball? Use $beach. Something smaller than a tennis ball? Use $pingpong. This is very intuitive, as I’d imagine everyone has played with all sorts of balls at some point, or at least are familiar of them from sports.
    But is a ping pong ball larger than a golf ball? Who knows? Further, a bowling ball and a soccer ball are actually the same size. So… again, not perfect.
    Scaling up to planets could work, but you would have to be knowledgeable in astronomy.
    How about straight-up numbers? We’re unable to use numbers alone because tools like stylelinter will protest. But would something like this work:
    $font-14: 14px;
    $font-16: 16px;
    $font-24: 24px;

    Well, it’s infinite as there is always room for new additions. But it’s also incredibly specific, and there are some downsides to have the actual value be part of the name like that. Let’s assume that $font-18 is used in a lot of places. And now, they say, all places with 18px must be changed to 19px (because reasons). Now we need to rename the variable from $font-18 to $font-19 then change the value of $font-19 from 18px to 19px. And that’s before we finally update all places using $font-18 to $font-19. This is almost like using raw CSS. Low score for maintainability.
    What about the animal kingdom?
    Mother Nature has provided a myriad of species on this earth, which comes in handy in this situation. Imagine something like this:
    $mouse: 12px;
    $dog: 16px;
    $hippo: 24px;

    Need something smaller than a mouse? Use $bee, $ant or $flea. Larger than a bear? Try $moose or $hippo. Larger than an elephant? Well, you have the $whale, or heck, we can go prehistoric and use $t-rex. There’s always an animal to squeeze in here. Very versatile, very intuitive, also infinite (almost). And fun, too — I wouldn’t mind doing font-size: $squirrel. ????
    But then again, even that might require needing to reference the variables, unless we know exactly which animals are contained in our zoo of font sizes. But maybe that’s not a big deal as long as it scales.
    I have spent way too much time pondering this
    Or have I? The code base is where you spend your working hours. It’s your work environment, just like chairs and monitors. And the workplace should be a nice place.
    How do you handle your font size scales? Do you have one system for fonts and another for things like margins? Can anyone jump right into your code and understand how everything is organized? Please tell in the comments!
    The post The Dilemma of Naming Font Size Variables appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

  6. # July 6, 2021

    Normally, a project will have a set of pre-determined font sizes, usually as variables named in such a way that seeks some semblance of order and consistency. Any project of considerable size can use something like that. There are always headings, paragraphs, lists, etc. You could set font sizes explicitly and directly everywhere (e.g. font-size: 18px). Raw CSS, as it were. I do see that occasionally — mixing not just sizes but also units like px, rem and em in mindless chaos.That’s why the CSS of a project typically uses variables or mixins — we’re shooting for structure, maintainability and, ultimately, consistency. We all know naming is hard and it doesn’t take looking much further than naming font size variables to see why. How should we name a small font size variable so it’s clear that it’s smaller than a large font size variable? And what happens if we need to insert a new variable in between them — is that one named in a way that clearly explains its relationship to the other size variables?We’ll continue talking about naming font size variables in this post. But, really, the issue extends beyond font sizes to any sort of size or length value. Think paddings, margins, widths, heights, border radii, etc. These things need structure and consistency, too!
    How do you define font sizes in your project? Does it look something like this with custom variables::root {
    /* Font size variables */
    --small: 12px;
    --medium: 16px;
    --large: 24px;
    }
    Or perhaps in Sass (which is what we’ll be using throughout this article) you might have variables for $small, $medium, and $large font sizes.Fine. After a while, let’s say the designer adds a new <h1> heading for a hero section. And it is very large. Larger than anything you have in the project. No problem, you reply. You add an $xlarge to the project, and go about your day.The following day, the designer makes a nice form label, which again has a new font size. This new size, though, is larger than small, but smaller than medium.Here we go.What should you call it? $small-medium? $small-2? $smedium? Whatever you name it, you won’t be happy with it. Because there is no word for that.Or should you maybe refactor it? Create a new $xsmall, and change all instances of $small to $xsmall? And then you can use $small for the form label? There’s a small risk that you will forget to change somewhere and, hey, presto: a bug. What happens next time, when something is introduced that has a larger size than the $medium variable value? Do we have to refactor $large and $xlarge too?I suggest adhering to a scale, always. An easy fix would be to further abstraction, perhaps ditching numbers and sizes in favor of functional names, like $form-label instead of $small-2 or $xsmall.But imagine having a set of font sizes like this:$small: 12px;
    $form-label: 14px;
    $medium: 16px;
    $large: 24px;
    That is a broken scale. It’s a size concept and a component concept mixed together. It raises questions. Should an alert or a button be allowed to use $form-label? Yuck.Maybe you have a Greek thing going on, naming the variables $alpha, $beta, $gamma? Let me ask you then, what is then bigger than $alpha? $alpha-large? Or wait, is $alpha the small one?I have also seen names like $button-font-size, $label-font-size, $blockquote-font-size. That seems to me like one variable per element used, instead of a scale, and sounds like it could be a lot of duplicated code if the same value is being used in multiple places, but with different names.Perhaps you’re working with one base font size and percentages only? Sure, but I would say you need variables for the percentages. That’s how Geoff handles font sizing and even he admits that the setup raises his own eyebrows. Calculations with subjectively-named variables might be clear to you, but crazy-looking and complicated for anyone else jumping into the project.h1 {
    font-size: clamp(var(--text-size-large), calc(var(--text-size-base) * var(--text-size-scaler)), var(--text-size-huge));
    }
    We need a better systemAdding and removing stuff constantly is the way we want to work. This is modern day development — MVP, Agile, and all the other hot buzzwords.What we need is a scale syntax that allows room for changes. Adding a new size to the scale should be easy without introducing breaking changes. I’m thinking of a kind of scale that is both flexible and infinite. It must be more sophisticated than $small, $medium and $large.It should be also be descriptive and intuitive. Preferably, you shouldn’t have to look up the variable names in the settings file or the config, or wherever you store these things. I don’t have the slightest clue if $epsilon comes before or after $sigma. Do you?Using existing systemsBefore trying to invent something new, is there an existing syntax or system we can leverage? Here are a few I’ve encountered.International system of unitsSurely, you’re familiar with terms like “kilobyte” and “megabyte.” Europeans are very used to “millimeter” and “centimeter.” Other examples are “giga,” “tera,” and “peta.” These prefixes can be used for length, weight, volume and more. Could a $centi font size work? It is intuitive to a certain extent, that is, if you’re familiar with the metric system. This is a finite scale. And there’s no room to add new sizes because they are already set.Traditional point-size namesLong before computers and desktop publishing, books and newspapers were printed with lead type. The type setters had different names for different sizes. The sizes have a reference to a point size (pt) and could, in theory, be used for pixel sizes (px).The type sizes in this system are called “Nonpareil,” “Pica,” “Cicero,” and “Great Primer,” just to name a few. The names are different depending on continent and country. Plus, the same name can have different sizes, so… quite confusing.That said, I do like this system in a way because it would be like paying respect to an old craftsmanship from times past. But the names are so weird and specifically meant for type sizing, that it feels like a stretch to use for things like breakpoints and spacing.Placing everyday objects on a scaleHow about using stuff from our everyday life? Say chili peppers.There are many kinds of chili peppers. The $habanero, is hotter than the $cayenne, which is hotter than the $jalapeno. That would be fun, yeah?But as much as I enjoy the idea of writing font-size: $tabasco, I see two problems. If you’re not into peppers, you cannot know which pepper is hotter than another pepper — so, it’s not universally intuitive. Also, the bell pepper is 0 on the Scoville scale, and nothing is below that. Carolina Reaper is the hottest pepper in the world, so the scale is finite.And yeah, peppers scale-wise are not larger or smaller, they are hotter. Bad concept. Maybe something more common, like types of balls?There‘s a large range of different kinds of balls. You have handballs, soccer balls, volleyballs, etc. Need something larger than a medicine ball? Use $beach. Something smaller than a tennis ball? Use $pingpong. This is very intuitive, as I’d imagine everyone has played with all sorts of balls at some point, or at least are familiar of them from sports.But is a ping pong ball larger than a golf ball? Who knows? Further, a bowling ball and a soccer ball are actually the same size. So… again, not perfect.Scaling up to planets could work, but you would have to be knowledgeable in astronomy.How about straight-up numbers? We’re unable to use numbers alone because tools like stylelinter will protest. But would something like this work:$font-14: 14px;
    $font-16: 16px;
    $font-24: 24px;
    Well, it’s infinite as there is always room for new additions. But it’s also incredibly specific, and there are some downsides to have the actual value be part of the name like that. Let’s assume that $font-18 is used in a lot of places. And now, they say, all places with 18px must be changed to 19px (because reasons). Now we need to rename the variable from $font-18 to $font-19 then change the value of $font-19 from 18px to 19px. And that’s before we finally update all places using $font-18 to $font-19. This is almost like using raw CSS. Low score for maintainability.What about the animal kingdom?Mother Nature has provided a myriad of species on this earth, which comes in handy in this situation. Imagine something like this:$mouse: 12px;
    $dog: 16px;
    $hippo: 24px;
    Need something smaller than a mouse? Use $bee, $ant or $flea. Larger than a bear? Try $moose or $hippo. Larger than an elephant? Well, you have the $whale, or heck, we can go prehistoric and use $t-rex. There’s always an animal to squeeze in here. Very versatile, very intuitive, also infinite (almost). And fun, too — I wouldn’t mind doing font-size: $squirrel. ????But then again, even that might require needing to reference the variables, unless we know exactly which animals are contained in our zoo of font sizes. But maybe that’s not a big deal as long as it scales.I have spent way too much time pondering thisOr have I? The code base is where you spend your working hours. It’s your work environment, just like chairs and monitors. And the workplace should be a nice place.How do you handle your font size scales? Do you have one system for fonts and another for things like margins? Can anyone jump right into your code and understand how everything is organized? Please tell in the comments!Source link Share this:TwitterFacebookLike this:Like Loading…

  7. # July 6, 2021

    […] base font size and percentages only? Sure, but I would say you need variables for the percentages. That’s how Geoff handles font sizing and even he admits that the setup raises his own eyebrows. Calculations with subjectively-named […]

  8. # July 7, 2021

    […] base font size and percentages only? Sure, but I would say you need variables for the percentages. That’s how Geoff handles font sizing and even he admits that the setup raises his own eyebrows. Calculations with subjectively-named […]

  9. # October 19, 2022

    Very nicely explained

    Help me in improving looks of my site

Comments are closed.