Menu Search
Jump to the content X X
Smashing Conf Barcelona 2016

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Responsive Typography With Sass Maps

Managing consistent, typographic rhythm isn’t easy, but when the type is responsive, things get even more difficult. Fortunately, Sass maps make responsive typography much more manageable.

Writing the code is one thing, but keeping track of font-size values for each breakpoint is another — and the above is for paragraphs alone. Throw in h1 to h6s, each with variable font sizes for each breakpoint, and it gets cumbersome, especially when the type doesn’t scale linearly.

If you’ve tried to tackle responsive type, this may look familiar:

p { font-size: 15px; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 640px) {
  p { font-size: 17px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; }
}

Sass variables are great for making values reusable throughout a project, but managing them for responsive font sizes easily becomes a mess.

$p-font-size-mobile : 15px;
$p-font-size-small  : 16px;
$p-font-size-medium : 17px;
$p-font-size-large  : 19px;

$h1-font-size-mobile: 28px;
$h1-font-size-small : 31px;
$h1-font-size-medium: 33px;
$h1-font-size-large : 36px;

// I think you get the point…

This is where Sass maps1 and loops are powerful: They’ve helped me manage z-index values2, colors3 and, as you’ll see in a moment, font sizes.

Organizing Font Sizes With Sass Maps Link

Let’s start by creating a Sass map with key-value pairs — breakpoints as keys and font sizes as corresponding values.

$p-font-sizes: (
  null  : 15px,
  480px : 16px,
  640px : 17px,
  1024px: 19px
);

With mobile-first in mind, we see that the key null represents the default font size (not in a media query), and breakpoints are in ascending order.

Next, the mixin, which iterates through a Sass map and generates the appropriate media queries.

@mixin font-size($fs-map) {
  @each $fs-breakpoint, $fs-font-size in $fs-map {
    @if $fs-breakpoint == null {
      font-size: $fs-font-size;
    }
    @else {
      @media screen and (min-width: $fs-breakpoint) {
        font-size: $fs-font-size;
      }
    }
  }
}

Note: It’s worth mentioning that this mixin, along with the ones to follow, feature some basic programming logic. Sass, with the help of SassScript4 (a set of extensions that comes baked in), makes basic programming constructs possible, like if/else statements, each loops and a ton more. I encourage you to take some time to read through the documentation5. Sass’ “power features” will introduce you to a new dimension of things you can do with Sass.

We’ll then use the mixin for paragraphs:

p {
  @include font-size($p-font-sizes);
}

… which results in the following CSS:

p { font-size: 15px; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 640px) {
  p { font-size: 17px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; }
}

Managing and keeping track of font sizes for elements becomes a whole lot easier! With every new element, create a map and call the mixin in the appropriate selector.

$h1-font-sizes: (
  null  : 28px
  480px : 31px,
  640px : 33px,
  1024px: 36px
);

h1 {
  @include font-size($h1-font-sizes);
}

Keep font sizes consistent for various elements:

p, ul, ol {
  @include font-size($p-font-sizes);
}

Solving Breakpoint Fragmentation Link

But wait! What if we decide that we want the font size of ps to be 17 pixels and of h1s to be 33 pixels at a breakpoint of 700 pixels, instead of 640 pixels? With the solution above, that would require manually changing every instance of 640px. By trying to solve one problem, we’ve inadvertently created another: breakpoint fragmentation.

If we can manage font sizes in Sass maps, surely we can do the same with breakpoints, right? Exactly!

Let’s create a map for common breakpoints and assign each value an appropriate name. We’ll also change the font-sizes map a bit by using the breakpoint names we assigned in $breakpoints to establish a relationship between the breakpoints and font-sizes maps.

$breakpoints: (
  small : 480px,
  medium: 700px, // Previously 640px
  large : 1024px
);

$p-font-sizes: (
  null  : 15px,
  small : 16px,
  medium: 17px,
  large : 19px
);

$h1-font-sizes: (
  null  : 28px,
  small : 31px,
  medium: 33px,
  large : 36px
);

The last step is to tweak the mixin a bit so that when it iterates through the font-sizes map, it’ll use the breakpoint name to get the appropriate value from $breakpoints before generating the media query.

@mixin font-size($fs-map, $fs-breakpoints: $breakpoints) {
  @each $fs-breakpoint, $fs-font-size in $fs-map {
    @if $fs-breakpoint == null {
      font-size: $fs-font-size;
    }
    @else {
      // If $fs-font-size is a key that exists in
      // $fs-breakpoints, use the value
      @if map-has-key($fs-breakpoints, $fs-breakpoint) {
        $fs-breakpoint: map-get($fs-breakpoints, $fs-breakpoint);
      }
      @media screen and (min-width: $fs-breakpoint) {
        font-size: $fs-font-size;
      }
    }
  }
}

Note: The mixin’s default breakpoints map is $breakpoints; if your breakpoints variable’s name is different, be sure to change it in the second argument of line 1.

Voila! Now, what if we want an element to have a font size for a custom breakpoint that doesn’t exist in $breakpoints? In the font-sizes map, simply drop in the breakpoint value instead of a name as the key, and the mixin will do the work for you:

$p-font-sizes: (
  null  : 15px,
  small : 16px,
  medium: 17px,
  900px : 18px,
  large : 19px,
  1440px: 20px,
);

p {
  @include font-size($p-font-sizes);
}

The magic happens in the mixin thanks to Sass’ map-has-key function6. It checks to see whether the key name exists in $breakpoints: If it exists, it’ll use the value of the key; if not, it’ll assume the key is a custom value and use that instead when generating the media query.

p { font-size: 15px; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 700px) {
  p { font-size: 17px; }
}
@media screen and (min-width: 900px) {
  p { font-size: 18px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; }
}
@media screen and (min-width: 1440px) {
  p { font-size: 20px; }
}

Improving Vertical Rhythm With Line Height Link

Line height is also an important part of achieving consistent vertical rhythm. So, without going overboard, let’s include line height in the solution.

Extend the font-sizes map by including both font size and line height in a list as the value of the desired key:

$breakpoints: (
  small : 480px,
  medium: 700px,
  large : 1024px
);

$p-font-sizes: (
  null  : (15px, 1.3),
  small : 16px,
  medium: (17px, 1.4),
  900px : 18px,
  large : (19px, 1.45),
  1440px: 20px,
);

Note: Although line-height values can be defined using any valid CSS unit (percentages, pixels, ems, etc.), “unitless” values are recommended7 and preferred8 in order to avoid unexpected results due to inheritance.

We then need to modify the mixin to include line height when generating the CSS.

@mixin font-size($fs-map, $fs-breakpoints: $breakpoints) {
  @each $fs-breakpoint, $fs-font-size in $fs-map {
    @if $fs-breakpoint == null {
      @include make-font-size($fs-font-size);
    }
    @else {
      // If $fs-font-size is a key that exists in
      // $fs-breakpoints, use the value
      @if map-has-key($fs-breakpoints, $fs-breakpoint) {
        $fs-breakpoint: map-get($fs-breakpoints, $fs-breakpoint);
      }
      @media screen and (min-width: $fs-breakpoint) {
        @include make-font-size($fs-font-size);
      }
    }
  }
}

// Utility function for mixin font-size
@mixin make-font-size($fs-font-size) {
  // If $fs-font-size is a list, include
  // both font-size and line-height
  @if type-of($fs-font-size) == "list" {
    font-size: nth($fs-font-size, 1);
    @if (length($fs-font-size) > 1) {
      line-height: nth($fs-font-size, 2);
    }
  }
  @else {
    font-size: $fs-font-size;
  }
}

The mixin checks to see whether the value of the key in the font-sizes map is a list as opposed to a font-size value. If it’s a list, then it gets the correct value from the list by index value, with the help of the nth function9. It assumes that the first value is the font size and the second is the line height. Let’s see it in action:

p {
  @include font-size($p-font-sizes);
}

And here’s the resulting CSS:

p { font-size: 15px; line-height: 1.3; }

@media screen and (min-width: 480px) {
  p { font-size: 16px; }
}
@media screen and (min-width: 700px) {
  p { font-size: 17px; line-height: 1.4; }
}
@media screen and (min-width: 900px) {
  p { font-size: 18px; }
}
@media screen and (min-width: 1024px) {
  p { font-size: 19px; line-height: 1.45; }
}
@media screen and (min-width: 1440px) {
  p { font-size: 20px; }
}

This final solution is easily extensible to accommodate a host of other attributes, such as font weights, margins, etc. The key is to modify the make-font-size utility mixin and use the nth function to get the appropriate value from the list.

Conclusion Link

There are various ways to approach responsive typography and consistent vertical rhythm, and they are not limited to my suggestion. However, I find that this works for me more times than not.

Using this mixin will likely generate duplicate media queries in your compiled CSS. There’s been a lot of discussion about duplicate media queries versus grouped media queries, using @extend instead of mixins10, and performance and file size; however, tests have concluded that “the difference, while ugly, is minimal at worst, essentially non-existent at best.”

I also realize that my solution is not robust (it’s not designed to handle media-query ranges, max-width or viewport orientation). Such features can be implemented in the mixin (my personal version also converts pixel values to ems), but for complex media queries, I prefer to write by hand. Don’t forget that you can use the map-get function11 to retrieve values from existing maps.

Alternatives Link

Viewport units12 (vh, vw, vmin and vmax) can also be used to create responsive typography:


An example of viewport units in action. One viewport unit = 1% of the viewport’s width or height. (For a 1000-pixel-wide viewport, 1vw = 10px; for a 500-pixel-high viewport, 1vh = 5px.)

For example, viewport-width units can be used to build fluid hero text13. However, because the text will be scaled to the width or height of the viewport (as opposed to the size of the content area of the page) and because CSS currently lacks min and max values for the font-size property, viewport units aren’t suitable for body text: No matter what value you choose, body text sized in viewport units will always end up being too large or too small at extreme browser sizes, necessitating intervention by media query.

FitText.js14 does a similar job, with a focus on sizing text so that it always rests on a single line or measure. SVG techniques can also be used to achieve a similar effect.

Finally, Erik van Blokland15 has been working on some very exciting possibilities for responsive typography16, such as letterforms that actually alter with viewport size to preserve space, rather than simply get smaller.

Further Resources Link

Modular Scale17 is a great tool to achieve responsive typography, and Sara Soueidan has a great article on responsive typography techniques18.

Image source19 of picture on front page.

(ds, ml, al)

Footnotes Link

  1. 1 https://jonsuh.com/blog/sass-maps/
  2. 2 https://jonsuh.com/blog/organizing-z-index-with-sass/
  3. 3 https://jonsuh.com/blog/sass-maps/#loops-and-maps
  4. 4 http://sass-lang.com/documentation/file.SASS_REFERENCE.html#sassscript
  5. 5 http://sass-lang.com/documentation/file.SASS_REFERENCE.html
  6. 6 http://sass-lang.com/documentation/Sass/Script/Functions.html#map_has_key-instance_method
  7. 7 https://css-tricks.com/almanac/properties/l/line-height/
  8. 8 https://developer.mozilla.org/en-US/docs/Web/CSS/line-height#Prefer_unitless_numbers_for_line-height_values
  9. 9 http://sass-lang.com/documentation/Sass/Script/Functions.html#nth-instance_method
  10. 10 https://tech.bellycard.com/blog/sass-mixins-vs-extends-the-data/
  11. 11 http://sass-lang.com/documentation/Sass/Script/Functions.html#map_get-instance_method
  12. 12 https://css-tricks.com/viewport-sized-typography/
  13. 13 http://demosthenes.info/blog/739/Creating-Responsive-Hero-Text-With-vw-Units
  14. 14 http://fittextjs.com/
  15. 15 https://twitter.com/letterror
  16. 16 http://letterror.com/dev/mathshapes/page_20_Excellence.html
  17. 17 http://www.modularscale.com/
  18. 18 http://tympanus.net/codrops/2013/11/19/techniques-for-responsive-typography/
  19. 19 http://www.flickr.com/photos/r2i-social-networking/6925301398/sizes/m/in/photostream/
SmashingConf Barcelona 2016

Hold on, Tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? Like SmashingConf Barcelona, on October 25–26, with smart design patterns and front-end techniques.

↑ Back to top Tweet itShare on Facebook

Advertisement

Jonathan is a developer & designer with a focus in front-end development. He’s been building the web and has watched it grow and evolve since the 90’s—the days of dial-up, FrontPage Express, AOL and Geocities. He’s now a front-end designer/developer and enthusiastic about front-end architecture (structure, workflows, build processes), web performance, and giving back to the community.

  1. 1

    Very nice article! I love CSS p0rn :)

    14
  2. 2

    David Hucklesby

    June 17, 2015 4:29 am

    I don’t know if anyone else has run into this problem, but when I tried increasing font-size at incremental breakpoints, I found that zooming or increasing the browser’s text size either keeps text the same size, or even reduces it. Not friendly at all.

    Is there a cure for this?

    3
    • 3

      The examples use pixel measurements, which are really bad for correctly supporting user-zooming. Set you fonts and breakpoints in EMs instead.

      5
      • 4

        Interesting you bring that up, Matt—it’s something I considered when drafting the post. I decided to use pixels because I think many think in absolute units as opposed to relative units. Its primary purpose is to illustrate how maps can be used whereas the actual values themselves are trivial—can easily be swapped out or converted to values/units you’re familiar with or prefer.

        I personally use ems and rems, while remembering to use unitless values when I can.

        2
        • 5

          However you have to admit using px is suggesting some direction byond UX in some way. Why make one thing right while fail on the other?

          1
        • 6

          Henk C. Meerhof

          June 18, 2015 3:54 pm

          Still we are messing around the wrong way.
          And in fact the use of the EM(square) is just an other relative measurement. The only way to go would be that HTML/CSS and all other computer related measurement will relate again to real world measurements.
          As long we keep using things that are relative to something relative, we never end up with something useful (which is again relative).
          We can measure type, we have done it for ages, we learned a bunch, about human vision in relation to type, the measurements to use is the size we can see. This has nothing to do with px, % or em. It has all to do with mm (or pt).

          I know what Univers 9,5/12 pt looks like on paper. Why is it so damn difficult to get the same on a screen? The hardware already knows how many px there are in a mm, so just calibrate a screen not only on color, but also on measurement, and we all can use an ordinary ruler.

          It is not about how to get to your goal, but to finally get there!

          3
          • 7

            But that’s not all necessarily true, right? Because a pixel is a unit of angular measure, and people use different devices at different distances. I really am not the expert to pick apart your argument, but I’m not convinced it’s correct.

            1
  3. 8

    Jason Featheringham

    June 17, 2015 5:08 am

    Talk about a clumsy language syntax. I mean, could the creators not have used more contemporary grammar, such as $fs-breakpoints[$fs-breakpoint] instead of map-get($fs-breakpoints, $fs-breakpoint)? It’s far more readable and concise.

    0
    • 9

      When I find myself consistently needing to grab values from a map throughout my project, I create a custom mixin/function to make things a bit more readable, like such:

      @function z-index($key) {
      @return map-get($z-index, $key);
      }

      @mixin z-index($key) {
      z-index: z-index($key);
      }

      .navigation { @include z-index(navigation); }

      4
  4. 10

    Marcus Scheller

    June 17, 2015 6:24 am

    Interesting, thanks!

    1
  5. 12

    Rajesh Maharjan

    June 17, 2015 6:47 am

    Guys I found custom responsive css is far easy than bootstrap and SASS methods. You always need to design psd on bootstrap column base before making it bootstrap css.

    -9
    • 13

      No, it’s easier to use SASS and CSS than using Bootstrap and Photoshop, because Photoshop mixins and SASS grids are better than Photoshop and grids with CSS both combined, and therefore you should always use Sass Photoshop CSS Grid Mixins.

      0
  6. 14

    André Markus

    June 17, 2015 7:46 am

    Thanks for the article!
    Notice: The last image caption is not correct with your 1%. It should be “For a 1000-pixel-wide viewport, 1vw = 10px”, not 100px.

    2
  7. 16

    Florence Bell

    June 17, 2015 12:38 pm

    After reading this content, I am very much benefited. Keep sharing.

    2
  8. 17

    Tyler Morrison

    June 17, 2015 2:02 pm

    Fantastic article! Thanks for sharing. I’ve used Sass mixins to create my own modular type, but it felt a bit opinionated once I added it to real-world designs. Inevitably, there was always a case that needed some fine-tuning, but I felt stuck in the modular box that I had constructed for myself.

    I’m excited to try this out and see if I can possible merge the two. Thanks!

    3
    • 18

      Hey Tyler, thanks! I agree that sometimes solutions for one project may not necessarily for another; and while it may work for one person, it may not work for another or a team, which is OK.

      I think the important thing is that it not only work but also is maintainable and done consistently, especially with a team of people, and that is subjective based on the kind of project and people you work with.

      1
  9. 19

    James Morris

    June 17, 2015 5:31 pm

    This kind of thing is handy and I might use it but what considerations have you made for the learnability of your code?

    If we take this approach on ever bit of CSS we do our colleagues both senior and junior have to learn the code, thus read it and understand it. This takes longer and increases the maintenance of just using some variables and media queries?

    I’m all up making things clever and efficient but just concerned if we make things convoluted. Take for example a designer who’s being asked to produce code but just knows a bit of CSS and HTML. How do we leave the door open for them?

    1
    • 20

      Great thoughts, James.

      I think one key to making it work is documentation and communication to establish consistency—clearly documenting how to use the mixins/functions with examples to help those unfamiliar understand (It may not be necessary for everyone to know the ins-and-outs of the code itself, although it helps to know what’s happening under the hood). Then it’s important that everyone follows it consistently so the code doesn’t get fragmented. There may be an initial learning curve, but its intention is to make things easier and less convoluted, and I think the outcome would vary based on the dynamics of the team (which means this may or may not work).

      Trying out on a smaller project as opposed to a large one may be better—give it a whirl and get feedback to see if the others see value and buy in.

      3
  10. 21

    Mike Riethmuller

    June 18, 2015 12:48 am

    Hi Jonathan, thanks for this! I particularly liked your tips on solving breakpoint fragmentation.

    You also briefly mentioned viewport units and said:

    “…because CSS currently lacks min and max values for the font-size property, viewport units aren’t suitable for body text…”

    I’ve got a technique that directly addresses this limitation:

    http://madebymike.com.au/writing/precise-control-responsive-typography/
    http://madebymike.com.au/writing/fluid-type-calc-examples/

    Might be useful until we get min and max font-size?

    8
  11. 22

    This is a great approach to managing responsive typography, thank you so much for sharing. This is how I structure my typography for all of my projects. This idea can really be expanded on to include other things such as margin top and margin bottom. I actually created a little tool that manages typography just like this article, I just wanted to share just in case anyone finds it helpful.

    0
  12. 23

    Nicolas Hoizey

    June 18, 2015 2:09 pm

    Great article, with lovely details about Sass power.

    However, regarding responsive typography, you could mix both browser default value (using em or rem) and viewport relative values.

    On my own website, I use this:

    body {
    font-size: calc(0.8em + 0.3vw + 0.3vh);
    }

    -5
  13. 24

    David Fairbrother

    June 19, 2015 9:36 am

    Is this not overcomplicating it?

    Why not just set your paragraph and heading (and anything else) elements as relative (e.g. in ems) to a base/body font size?

    At a few – you shouldn’t need many – breakpoints simply adjust that base/body font size. That one change will affect all the other typographic elements relatively.

    If you want absolute control set the base/body font size as a px measuremenet. Otherwise let the browser/device determine what’s optimum.

    If you want to be really thorough, define your layout based on that body/base font size or line height – e.g. padding: 3em – then you’ll have delicious harmony of everything…

    0
    • 25

      If you have a linear font size scale, then yes I agree—this complicates things. However, there are many instances where I don’t want the font sizes to scale linearly or the font elements don’t follow the same linear scale, and I want to really fine tune each breakpoint I deem important enough to require a font-size change. For example:

      small screens:
      p: 16px
      h1: 30px
      h2: 24px

      medium screens:
      p: 18px
      h1: 35px
      h2: 27px

      large screens:
      p: 20px
      h1: 40px
      h3: 30px

      Each element has its own linear scale, so even if the values were in ems, it’s not as easy as just changing the base font size of one element.

      1
      • 26

        Davide Morotti

        July 22, 2015 3:06 pm

        I’m wondering, what could be a good reason to opt for a non-linear typographic scale?
        Would not be better maintain proportions as much as possible?

        0
  14. 27

    This is very cool and I have implemented it, but I am curious, if you extrapolate this and use it for multiple tags like P, H1, H2, etc. you eventually end up with a lot of excessive media queries. Is there a way to combine it all using lists for each tag but run it through only once so I only have one media query with those tags for each breakpoint?

    1
  15. 30

    David Plunkett

    June 22, 2015 11:13 am

    Great article. Not sure if got mentioned somewhere else, but this is a great Gulp task for combining all your media queries to keep the final CSS size down.

    https://www.npmjs.com/package/gulp-combine-media-queries

    0
  16. 31

    hypeJunction

    June 22, 2015 2:38 pm

    Good start, but as suggested above em’s are much easier to manage. All you need to do is define body font-size in pixels for each breakpoint, from there on, you use em’s or rem’s. I would suggest looking at how Zurb’s Foundation deals with that issue.

    Also, I find it practical to let the browser decide what the appropriate line height is by setting it to normal all across. Trying to deal with line heights in responsive layouts is a headache.

    0
  17. 33

    Nice sass example, but completely pointless. The only real way to deal with font sizes is to LEAVE THEM ALONE! Each and every platform has a user config to allow users to specify a font size they feel comfortable with.
    Thus, every design(er) should never ever define a font-size in px.
    You should always use (r)ems to provide a user experience based on what the users chose instead of an arbitrarily chosen font size (even commonly accepted).

    -5
    • 34

      hypeJunction

      June 23, 2015 9:32 am

      Can’t agree with giving users full control. Welcome back to MySpace era.

      -1
    • 35

      Making the values ems doesn’t fix the problem. I may be missing something, but the value here is not px or (r)em—it’s management of the values (px or (r)em) across various breakpoints. I used px in this post because it’s easier to follow mentally (I use ems, but I think in px). And concerning leaving the font sizes alone, if you give an element a custom font size, you’re overriding the default value the browser gives, even if the value is in ems.

      If you don’t like px and want to use ems, easily change the values to use ems, or like I do it, tie in a function to convert the px values to ems. If you don’t find value in it, then I can understand, but calling it useless is a stretch.

      5
  18. 36

    Adrian Bettridge-Wiese

    June 23, 2015 4:06 pm

    A quick additional note about avoiding viewport units: they don’t respond to browser zoom commands, so they aren’t accessible.

    3
  19. 37

    Kris Van herzeele

    June 24, 2015 8:15 am

    Another option would be to put your units in rem and then define your font sizes in the html tag trough media query’s, that looks the most easy way to me. Would be good to mention it.

    0
  20. 38

    Thankyou. it great article

    2

↑ Back to top