How to Build a Responsive Type Scale with Bootstrap

In this tutorial, we’ll be taking an in-depth look at how Bootstrap handles typography and how we can modify the code in a couple of different ways to create a responsive type scale. This is often referred to as “responsive typography”, the aim of which is to keep your typography readable on all screen sizes and avoid giant headings on mobiles!

How Bootstrap Sets Up Typography by Default

To understand the way Bootstrap typography works, we need to begin looking into the source SCSS files to explore the setup and default settings.

Note: for the sake of clarity throughout this tutorial, I’ve commented out styles from the Bootstrap code that are NOT associated with typography.

The html Element

Let’s first look at styles for the root element, found in _reboot.scss on line 27:

html {
  font-family: sans-serif;
  line-height: 1.15;
  -webkit-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
  // -ms-overflow-style: scrollbar;
  // -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

From the html element there’s not much to report in terms of setting up a type scale. However, it’s worth noting the -*-text-size-adjust: 100%; declarations. These have been used to prevent some mobile browsers from increasing the font size of their own accord.

The body Element

Here are the body element styes, as found in _reboot.scc on line 57:

body {
  // margin: 0; // 1
  font-family: $font-family-base;
  font-size: $font-size-base;
  font-weight: $font-weight-base;
  line-height: $line-height-base;
  color: $body-color;
  text-align: left; // 3
  // background-color: $body-bg; // 2
}

Now we can start to see some typographic styes being applied. In terms of type scale, we only need to be concerned with font-size. By default, this is set via the $font-size-base variable found in _variables.scss, and is equal to 1rem.

The p Element

These styles for the p element are found in _reboot.scss on line 109:

p {
  margin-top: 0;
  margin-bottom: $paragraph-margin-bottom;
}

Not much to report here: the p tag simply inherits its font-size and line-height from the body as you would expect.

The h1 through h6 Elements

These styles are found in _type.scss from lines 16 to 21:

h1, .h1 { font-size: $h1-font-size; }
h2, .h2 { font-size: $h2-font-size; }
h3, .h3 { font-size: $h3-font-size; }
h4, .h4 { font-size: $h4-font-size; }
h5, .h5 { font-size: $h5-font-size; }
h6, .h6 { font-size: $h6-font-size; }

You can see here the element and the utility class are given the font-size through a variable. The corresponding variables are found in _variables.scss on lines 246 to 251. Looking at these variables, we can see the default sizes set out to work across all browser and viewport widths:

$h1-font-size: $font-size-base * 2.5 !default;
$h2-font-size: $font-size-base * 2 !default;
$h3-font-size: $font-size-base * 1.75 !default;
$h4-font-size: $font-size-base * 1.5 !default;
$h5-font-size: $font-size-base * 1.25 !default;
$h6-font-size: $font-size-base !default;

Looking at a type scale for the above, it’s clear that an increase of .25rem is used until the h1, which is given a .5rem increase.

These sizes can be overridden in a custom variables file if you’re using a Sass compiler, but it still leaves you with one font-size for each heading across all browser and viewport widths.

The .display-1 through .display-4 Utility Classes

The following code is fond in _type.scss from lines 29 to 48:

// Type display classes
.display-1 {
  font-size: $display1-size;
  // font-weight: $display1-weight;
  // line-height: $display-line-height;
}
.display-2 {
  font-size: $display2-size;
  // font-weight: $display2-weight;
  // line-height: $display-line-height;
}
.display-3 {
  font-size: $display3-size;
  // font-weight: $display3-weight;
  // line-height: $display-line-height;
}
.display-4 {
  font-size: $display4-size;
  // font-weight: $display4-weight;
  // line-height: $display-line-height;
}

As with the heading elements, the display utility class sizes are defined as variables in the _variables.scss file on lines 259 to 262. Again, if you’re working with a Sass compiler you can override these in a custom variables file.

That about covers the setup from Bootstrap, and we can now look at ways of making a responsive type scale that’s quickly adjustable.

Creating the Responsive Type Scale

It’s worth expanding on what I mean by a Responsive Type Scale. By default, Bootstrap font-size for headings and its display-* classes are explicitly set using variables found in _variables.scss and rules found in _type.scss.

Setting one font-size outright for headings across all screen and viewport sizes can quite quickly lead to oversized headings that make for a poor user experience.

You could, of course, create some media queries when it suits to pull down font sizes that look over-sized, but it’s at this point that you lose any form of a type scale hierarchy. We need type hierarchy to follow the flow of the document.

In comes the Responsive Type Scale and a nice Sassy way to implement it into a Bootstrap project! If you don’t use Sass or SCSS, you can simply update the pen I use for examples and extract the compiled CSS.

An Overview of the Responsive Type Scale for Bootstrap

We are going to set up three main things:

  • a type scale map for quick changes and experiments
  • a function to check if the scale is valid for use
  • two mixins to allow us the flexibility of adjusting the font sizes at any given time.

It’s worth noting that, should your design not include a type scale that works on a common multiple, using the mixin won’t work and you’ll need to look at the compiled CSS in the pen to get the code you need to update the font sizes.

The Responsive Type Scales Map

Create a Sass map of pre-defined typographic scales, $type-scales, according to the model found on type-scale.com. The scales in the map can be passed to the Sass mixin that creates the font sizes by using their key from the key: value pairs.

After the map of scales, two variables are defined: $heading-type-scale-base and $display-type-scale-base. These variables hold the initial scales that are used from a zero-width viewport or browser and upward. These variables accept a key from the $type-scales map or can be passed a unitless value:

$type-scales : (
  minor-second: 1.067,
  major-second: 1.125,
  minor-third: 1.200,
  major-third: 1.250,
  perfect-fourth: 1.333,
  augmented-fourth: 1.414,
  perfect-fifth: 1.500,
  golden-ratio: 1.618
);

$heading-type-scale-base : minor-third;
$display-type-scale-base : minor-third;

How to Check the Responsive Type Scales Value

It’s important that you aren’t restricted to only the values in the map, as that may not be suitable for your design.

For this reason, the function below will check if the value passed to the mixin is one of the values set in the $type-scales map or it must be a unitless value to create the type scale:

@function check-type-scale-value($scale) {

  // Check $scale against the values in $type-scales.
  @if map-has-key($type-scales, $scale) {

    // If the value of $scale is defined in
    // $type-scales, return the value of $scale.
    @return map-get($type-scales, $scale);

  // If the value of $scale is not defined in the
  // $type-scales map, check if the value is a number
  // and that the number is a unitless value.
  } @else if type-of($scale) == number and unitless($scale) {

    // If the value of $scale is a unitless number,
    // return the number.
    @return $scale;

  // Lastly, should the value passed to $scale be neither
  // found in the $type-scales map nor a unitless number,
  // throw a Sass error to explain the issue.
  } @else {

    // Throw a Sass error if the $scale value is
    // neither found in the $type-scales map nor
    // a unitless number.
    @error "Sorry, `#{$scale}` is not a unitless number value or a pre-defined key in the $type-scales map.";
  }

}

Next we will build up the mixins for creating the initial font sizes.

Creating the Heading and Display Font Sizes

I first wrote the CSS to achieve a responsive type scale when the alpha version of Bootstrap 4 was available. But since the release of the stable version of the library, I’ve revamped things to use the default SCSS setup, which makes it convenient to include the code in Bootstrap projects.

The first mixin is used to create the heading font sizes from h6 to h1:

@mixin create-heading-type-scale($scale) {

    // Check the $scale value and store in a variable to be
    // used when calculating the font sizes.
    $the-heading-type-scale: check-type-scale-value($scale);

    // Starting from h6, multiply each previous value by the scale
    // to get the next font size
    $font-size-h6 : $font-size-base;
    $font-size-h5 : $font-size-h6 * $the-heading-type-scale;
    $font-size-h4 : $font-size-h5 * $the-heading-type-scale;
    $font-size-h3 : $font-size-h4 * $the-heading-type-scale;
    $font-size-h2 : $font-size-h3 * $the-heading-type-scale;
    $font-size-h1 : $font-size-h2 * $the-heading-type-scale;
    // $font-size-display-base is made global to allow for accessing the
    // varibale in the next mixin.
    $font-size-display-base : $font-size-h1 !global;

    // Add the created font sizes to the elements and classes
    h1, .h1 { font-size: $font-size-h1; }
    h2, .h2 { font-size: $font-size-h2; }
    h3, .h3 { font-size: $font-size-h3; }
    h4, .h4 { font-size: $font-size-h4; }
    h5, .h5 { font-size: $font-size-h5; }
    h6, .h6 { font-size: $font-size-h6; }
}

Above, the font sizes are first created and stored in variables starting from the $base-font-size and multiplying each previous value by the type scale value. The same principle is applied below but we start from the $font-size-display-base:

@mixin create-display-type-scale($scale) {

    // Store default type scale in a variable for calculations
    $the-display-type-scale: check-type-scale-value($scale);

    // Create variables to reference the previous font size
    $font-size-display-4 : $font-size-display-base + $font-size-base;
    $font-size-display-3 : $font-size-display-4 * $the-display-type-scale;
    $font-size-display-2 : $font-size-display-3 * $the-display-type-scale;
    $font-size-display-1 : $font-size-display-2 * $the-display-type-scale;

    // Add the created font sizes to the elements and classes
    .display-4 { font-size: $font-size-display-4; }
    .display-3 { font-size: $font-size-display-3; }
    .display-2 { font-size: $font-size-display-2; }
    .display-1 { font-size: $font-size-display-1; }
}

Drop the Root font-size

I like to drop the root font-size to 14px for mobiles and work my way up to 16px and then 18px. Below is the SCSS to do so using the breakpoint sizes of md and lg:

html {
    font-size: 14px;
    @media (min-width: 768px) {
        font-size: 16px;
    }
    @media (min-width: 992px) {
        font-size: 18px;
    }
}

Wrapping It Up

Once all this work is done, it’s all about including your mixins and choosing your desired scales!

// Create the heading font sizes
@include create-heading-type-scale($heading-type-scale-base);

// Create the display font sizes
@include create-display-type-scale($display-type-scale-base);

// At the Bootstrap md breakpoint, adjust the heading font sizes.
@media (min-width: 768px) {
    @include create-heading-type-scale(minor-third);
}

As you can see, you can include the mixin at any breakpoint and completely change your type scale. Each font size can still be overridden if necessary and all looks good.

The Bootstrap Responsive Type Scale in Action

You can see the Bootstrap responsive type scale in action in this pen:

See the Pen Bootstrap 4 Responsive Type Scale by SitePoint (@SitePoint) on CodePen.

I hope this tutorial has given you an insight into how you can customize Bootstrap to work for you by providing a better understanding of how to achieve a responsive type scale using this popular library.

If you’ve got the basics of Bootstrap under your belt but are wondering how to take your Bootstrap skills to the next level, check out our Building Your First Website with Bootstrap 4 course for a quick and fun introduction to the power of Bootstrap.

Sponsors