Typography Handbook

A concise, referential guide on best web typographic practices.

Table of Contents


  1. Introduction
  2. Typographic Design
  3. Fonts
  4. Web Style Guide
  5. Conclusion

Introduction

Typography is more than just what fonts you use. Typography is everything that has to do with how text looks – such as font size, line length, color, and even more subtle things like the whitespace around a text. Good typography sets the tone of your written message and helps to reinforce its meaning and context.

This is a handbook on best typographic practices through the lens of a web designer. It is meant to be short and concise – used as a reference rather than explanatory. For in-depth explanations and details, look at the links in the Further Readings of each section instead.

Lastly, this handbook is open source on GitHub and will continuously be updated with the best practices. Enjoy.

Typographic Design

Visual Hierarchy

Visual hierarchy is the concept of organizing elements on a page in a way that establishes an order of importance, allowing readers to easily navigate the page and find relevant content. When done correctly, it should guide the reader's eye throughout different sections of the page. Visual hierarchy is very prevalent in typography, and forms the basis of typographic design theory.

Consider the following typographic design of Alice's Adventures In Wonderland, which clearly establishes a visual hierarchy:

Click here to view the web version of this design

Visual hierarchy can be broken down into 5 different parts:

  1. Size & Weight: Size and weight are the two easiest ways to create visual hierarchy. They easily indicate what is important, and readers' eyes naturally jump to them. By simply styling those two, a sense of importance is already created.
  2. Positioning: Positioning is another way to create visual hierarchy. Here, the title and author is above the rest of the text and centered, showing its importance.
  3. Typeface: Contrasting typefaces can create a distinction between different elements and achieve visual hierarchy.
  4. Color: Setting important text in a different color is a very easy way to create visual hierarchy. However, only do so sparingly as using color everywhere causes it to lose its distinction.

Further Readings:

Gestalt Laws in Typography

Gestalt principles, or gestalt laws, are rules of the organization of perceptual scenes. When we look at the world, we usually perceive complex scenes composed of many groups of objects on some background, with the objects themselves consisting of parts, which may be composed of smaller parts, etc. Scholarpedia

The two most important Gestalt Laws to understand in typography are the Law of Proximity and the Law of Similarity.

Law of Proximity

To master the positioning of elements, it is crucial to understand the Gestalt Law of Proximity. The Law of Proximity states that humans perceive objects that are closer together as related objects. And vice versa, objects that are farther apart are perceived as different groups.

Image source

In typographic design, "proximity" refers to the amount of whitespace created by line height, margin, and padding. There should be a distinctive and noticeable amount of additional whitespace between two different sections. Consider the following example:

Note that the Law of Proximity does not mean that you should squeeze related content in a small container. No, free flowing whitespace is important too. Simply add noticeable, additional whitespace between two separate sections.

Law of Similarity

The Gestalt Law of Similarity states that entities that look similar tend to be grouped together. For example, if all clickable texts are sky-blue, the audience will assume that all textual content that is sky-blue is clickable.

In typography, the law of similarity just means to keep your styles consistent on elements that serve the same function. Group of elements should also look similar to other groups that serve the same function, for example: the visual styles of one blog post should look similar to another blog post. On the other hand, elements with a different functions should not look similar to one another.

Further Readings:

Fonts

Choosing Fonts

Choosing fonts is a creative and emotional process. Different fonts convey different feelings, and you want a font that complements the tone of your text.

  • Start by finding a good font for your body text. When combining multiple fonts, keep your body font constant, and try to find other fonts that go well with it
  • Use tools such as TypeTester and TypeCast to help you experiment
  • Get inspired by others! Fonts In Use features a large collection of great font choices.
  • Some fonts were designed to be used as large-size headings, while others were designed to be used in small-density screens. Use fonts in their intended roles. WebType is a great resource for finding the “intended size” of different fonts. In addition, TypeKit labels its font as either Heading or Paragraph.

Further Readings:

Using Web Fonts

To declare custom web fonts, use the following syntax:

@font-face {
  font-family: 'Helvetica Neue';
  src: url('/assets/fonts/HelveticaNeue-Light.eot');
  src: url('/assets/fonts/HelveticaNeue-Light.eot?#iefix') format('embedded-opentype'),
    url('/assets/fonts/HelveticaNeue-Light.woff2') format('woff2'),
    url('/assets/fonts/HelveticaNeue-Light.woff') format('woff'),
    url('/assets/fonts/HelveticaNeue-Light.ttf') format('truetype');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Helvetica Neue';
  src: url('/assets/fonts/HelveticaNeue-Bold.eot');
  src: url('/assets/fonts/HelveticaNeue-Bold.eot?#iefix') format('embedded-opentype'),
    url('/assets/fonts/HelveticaNeue-Bold.woff2') format('woff2'),
    url('/assets/fonts/HelveticaNeue-Bold.woff') format('woff'),
    url('/assets/fonts/HelveticaNeue-Bold.ttf') format('truetype');
  font-weight: bold;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Helvetica Neue';
  src: url('/assets/fonts/HelveticaNeue.eot');
  src: url('/assets/fonts/HelveticaNeue.eot?#iefix') format('embedded-opentype'),
    url('/assets/fonts/HelveticaNeue.woff2') format('woff2'),
    url('/assets/fonts/HelveticaNeue.woff') format('woff'),
    url('/assets/fonts/HelveticaNeue.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
  • It is recommended to use all of the listed formats above for maximum compatibility. Otherwise, simply using woff2 and woff will support most modern browsers.
  • You must have a font file for each listed format. Use Transfonter or FontSquirrel’s Web Font Generator to generate all file formats from a single one.
  • Compress your fonts when possible. See here for more information.
  • Combine multiple type-families (light, regular, semibold, bold, etc) into one font-family, instead of having a different font-family name for each type-family.

Alternatively, you can also import fonts using an online web font service, such as Google Fonts or Typekit.

Further Readings:

Font Loading

Before custom fonts are displayed, they need to be loaded first. There are three possible scenarios for font loading:

  1. The font family is not recognised and a fallback font is applied.
  2. The font family is recognised but not yet loaded, and will be applied when it has finished downloading.
  3. The font family is recognised and has already been loaded and will be applied immediately.

Scenario 1 only happens when you try to use a nonexisting font, or a declaration with a bad src. This can and should be avoided entirely. Jumping to Scenario 3, it is the best case scenario and can usually be achieved through proper font caching. Scenario 2 is the scenario that involves font loading. Font loading is mostly unavoidable (at least for the first request instance). There are several ways to deal with it:

1. Flash of Unstyled Text (FOUT). A FOUT is an instance where a web page uses default and fallback fonts before switching to the proper web font. It happens because font requests do not happen until both HTML and CSS are downloaded. This means that there is a period of time where HTML is displayed before fonts are fully downloaded. The FOUT is the optimal approach for most websites, mainly because the alternatives are a lot worse. When done correctly, a FOUT is hardly noticeable.

2. Flash of Invisible Text (FOIT). A number of years ago, some modern browsers started to implement a new technique of dealing with font loading — the FOIT. A FOIT is an instance when the browser detects that a font is currently loading, and hides the text until font loading is complete. There is usually a maximum wait time before the browser switches to a fallback. This approach should always be avoided. Although it might sound good in theory, it can provide an awful user experience for people with slower internet. It can cause a FOUT after the initial FOIT, and at worst can even lead to permanent invisible content.

3. The Whitescreen Approach. The entire web page does not display until fonts are loaded. Alternatively, a loading progress bar can be displayed. This approach is ONLY recommended if a FOUT is going to heavily detract from the user experience of your audience. This is usually the case if the web page heavily relies on very distinct fonts in large sizes. Otherwise, a FOUT is preferred because content is king. This approach is similar to FOIT, but superior because you control when to start showing content instead of the browser. In FOIT, invisible text might also confuse the audience, whereas a completely white screen (or a progress bar) is an obvious sign of loading.

Whether you plan on going with the FOUT approach or the Whitescreen approach, you will want to use a JavaScript library called Web Font Loader. Web Font Loader gives you added control over @font-face, and adds events for you can control the font loading experience.

Note: There is a W3C Font Loading API that achieves similar goals, but its support is still poor.

FOUT Approach

The easiest way to font-load with the FOUT approach is by making use of the recently added font-display CSS property. font-display instructs the browser how the font should be displayed while it is in the downloading state, loading state, or ready state. The value that we want for the FOUT approach is font-display: swap. This tells the browser to render a fallback font until the main font is ready.

@font-face {
  font-family: 'Helvetica Neue';
  src: url('/assets/fonts/HelveticaNeue-Light.eot');
  font-display: swap;
}

As font-display is still relatively new, browser support for it is limited. Check here to see if your target browsers support it.

A more widely-supported method of achieving FOUT is using JavaScript to append a CSS class once font is ready. Here is an example of this done using Web Font Loader:

<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
<script type="text/javascript">
  WebFontConfig = {
    google: { families: [ 'Lora' ] }
  };
</script>

<style>
  p {
    font-family: "Arial";
  }

  .wf-active p {
    font-family: "Lora, Arial";
  }
</style>

In the above code, the browser first renders paragraph texts with Arial. Meanwhile, the Web Font Loader library starts loading Lora from Google Fonts. Once it is loaded, the wf-active class is appended to the html element, and the Lora font starts to be used for paragraph texts.

  • It is important to use Web Font Loader asynchronously so it does not delay the render of the rest of the page
  • Style your fallback fonts to appear as close as possible to your actual fonts to minimize the effects of the FOUT. See here for a list of usable fallback fonts. Use this tool to easily compare your fallback font to your custom font.

Whitescreen Approach

Here is an example of using Web Font Loader with the whitescreen approach:

<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.16/webfont.js"></script>
<script>
  WebFont.load({
    google: {
      families: ['Raleway', 'Oswald']
    }
  });
</script>

<noscript>
  <link href='https://fonts.googleapis.com/css?family=Raleway|Oswald' rel='stylesheet' type='text/css'>
</noscript>

<style>
  .wf-loading {
    display: none;
  }

  .wf-active p {
    font-family: "Arial";
  }

  p {
    font-family: "Raleway, Arial";
  }
</style>
  • In this case, the fallback does not need to appear similar to your custom font since there is no FOUT. Style the fallback as you see fit.
  • Use Pace if you want to include a progress bar instead of a white screen. This is significantly better for UX, especially if the font files are large.

Further Readings:

OpenType Features

OpenType features can be thought of as typographic options for the font. They can be used to enhance the legibility and appearance of text.

p {
  font-kerning: normal;
  font-variant-ligatures: common-ligatures contextual;
  font-feature-settings: "kern", "liga", "clig", "calt";
}
  1. OpenType features are built in the font. This means that different features will be available to different fonts. Check which features are availabe to the fonts that you are using.
  2. Use font-feature-settings to enable OpenType features.
  3. Kerning kern, ligatures liga, contextual ligatures clig, and contextual alternatives calt should always be enabled for all texts.

Further Readings:

Web Style Guide

Relative Units

Use relative units whenever possible.

html { font-size: 100% }
p { font-size: 1em }

@media (min-width: 64em) {
  html {
    font-size: 112.5%;
  }
}
  • font-size: 100% listens to the browser’s font size settings instead of overwriting it. By default in most browsers, this would place 1em at 16px.
  • Changing the value of the font-size of the html will also affect every em and rem element. This can be useful for implementing responsive web design.
  • User preference is important, so do not stray too far from font-size: 100% and 1em.
  • Use rem and em for font-size.
  • Use rem, em, or % for element positioning (margin, padding, etc).
  • Use em for media query dimensions.
  • For large headings or when text is grouped with an image, use FitText to achieve scalable headlines. Stray away from vw and vh due to incomplete support, difficulty in precise configuration, and because it does not listen to browser font or zoom settings.

Further Readings:

Containers

The container, also known as the wrapper, is an HTML element that encloses one or more other elements. It allows for grouping elements semantically, cosmetically, or in layout.

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

.container {
  max-width: 67rem;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
  • It is strongly recommended to use box-sizing: border-box. Click here for more information about it.
  • Using max-width with left and right padding is an easy way to create a mobile-friendly container.
  • Choose a suitable width that isn't too long (causes visitor's eye to have a hard time focusing) or short (causes visitor's eye to travel back too often). In web typography, there is no line length rule that works for every font, size, leading, and resolution. Use your own best judgment.

Further Readings:

Font Sizing

Use a modular scale to help you decide on the font-size of your elements. Modular scale refers to a series of harmonious numbers that relate to one another in a meaningful way.

A modular scale in action. Image source
  • The modular scale is only a guideline and can be thought of as just a starting point.
  • Keep in mind that different fonts have different cap heights and x-heights, and that most modular scale tools do not account for that.
  • In your stylesheets, provide a reference back to what modular scale you used in comments.

Responsive Modular Scale

A single modular scale will rarely look good on all resolutions. To remedy this, different scales can be used depending on the resolution of the viewer's device.

//Sass responsive modular scale


/* 
 * Modular scale
 * http://www.modularscale.com/?1.25&em&1.33&web&text
*/

$type-scale-large: (
  h1: 3.911rem,
  h2: 2.941rem,
  h3: 2.211rem,
  h4: 1.663rem,
  p: 1.25rem
);

/* 
 * Modular scale
 * http://www.modularscale.com/?1.25&em&1.25&web&text
*/

$type-scale-medium: (
  h1: 3.052rem,
  h2: 2.441rem,
  h3: 1.953em,
  h4: 1.563rem,
  p: 1.25rem,
);


/* 
 * Modular scale
 * http://www.modularscale.com/?1.1&em&1.25&web&text
*/

$type-scale-small: (
  h1: 2.686rem,
  h2: 2.148rem,
  h3: 1.719rem,
  h4: 1.375rem,
  p: 1.1rem
);


$breakpoint-medium: 75em;
$breakpoint-small: 45em;

@mixin size($level) {
  font-size: map-get($type-scale-large, $level);
  @media (max-width: $breakpoint-medium) {
     font-size: map-get($type-scale-medium, $level);
  }
  @media (max-width: $breakpoint-small) {
     font-size: map-get($type-scale-small, $level);
  }
}

// Example

.title {
  @include size(h1);
}

Further Readings:

Vertical Spacing

Vertical spacing is created by line-height, margin, and padding.

  • line-height should be unitless. Wide containers should have text with a larger line-height, while narrow containers look better with text with a smaller line-height.
  • Try to only apply margins in a single direction on textual elements, preferably margin-bottom.
  • Adhere to the Law of Proximity.

Vertical Rhythm

Vertical rhythm is the concept of keeping vertical spaces between elements consistent. It is incredibly important as it helps to create a visually relaxing experience, and evokes a feeling of familiarity to users.

Image source

Establishing a vertical rhythm is simple. First, decide on a base whitespace value that you will use for vertical margins and vertical padding. Then, apply this value as a single-direction margin (or padding) to your containers, textual elements, and other relevant elements. For larger gaps, use a multiple of the base value.

Setting the base vertical spacing to be the same size as the line-height will allow every line to fit in an imaginary baseline grid. This is often done to imitate the uniformity of print design. This is not a requirement of vertical rhythm; any value works for base vertical spacing as long as a multiple of it is repeated consistently.

body { 
  line-height: 1.4; // Base line height
}

p { 
  font-size: 1.25em; // Base font size
  margin-bottom: 1.75rem; // Base vertical spacing: (1.4 * 1.25) = 1.75
}

h1 {
  font-size: 3em;
  margin-bottom: 3.5rem; // Double the base value for a larger gap (1.75 * 2) = 3.5
}

h2 {
  font-size: 2em;
  margin-bottom: 1.75rem;
}

h3 {
  font-size: 1.5em;
  margin-bottom: 1.75rem;
}

.page-container {
  padding: 3.5rem 2rem; // 3.5 is double the base value
}
/* Simple Sass Implementation */

$base-line-height: 1.4;
$base-font-size: 1.25rem;
$vertical-rhythm: $base-line-height * $base-font-size;

body { 
  line-height: $base-line-height;
}

p { 
  font-size: $base-font-size;
  margin-bottom: $vertical-rhythm;
}

h1 {
  font-size: 3em;
  margin-bottom: $vertical-rhythm * 2;
}

h2 {
  font-size: 2em;
  margin-bottom: $vertical-rhythm;
}

h3 {
  font-size: 1.5em;
  margin-bottom: $vertical-rhythm;
}

.page-container {
  padding: ($vertical-rhythm * 2) 2rem;
}

Note that rem is used for spacing as it is not influenced by the font-size of the element.

Bottom Aligned Baseline Grid

The bottom aligned baseline grid is a stricter implementation of vertical rhythm. In web, text is vertically aligned to the center of the line-height. This can be troublesome for large text as there will be excessive space on the top and the bottom. In print, this issue is avoided as text is aligned to the bottom of the baseline grid.

It's also possible to fix the issue without a baseline grid by applying a negative margin-top and a smaller margin-bottom to large texts.

Image source

There is no easy way to apply a bottom aligned baseline grid that works for different typefaces, font-size, and resolutions. It is highly recommended to use a typographic baseline library such as Sassline or MegaType.

Remember that vertical rhythm is just a guideline, and that the baseline grid is imaginary. It does not need to be pixel perfect for every element, nor does it need to be followed at every instance.

Further Readings:

Color

Color provides a huge visual distinction and is an important part of typography.

  • Don't pick colors arbitrarily; use a color palette instead. Material Design colors and Flat UI colors are good palettes to start with.
  • Do not overuse a color too many times, or else they may lose their unique distinction. Do not use too many completely different colors either.
  • Adhere to the Law of Similarity.
  • It is recommended to not use pitch black #000 as your body text color. Instead, use a very dark gray such as #333.
  • Sometimes, it is better to use an alpha value or opacity instead of a lighter color. Click here for an in-depth explanation.
  • Make sure that there is enough contrast between the text and the background. Use this contrast checker tool to help you.

Further Readings:

Underlining

In a printed document, don’t underline. Ever. It’s ugly and it makes text harder to read. Practical Typography

By default, underlines don't look great in the web either. Fortunately, there is a method involving background-image to style underlines to make them look appealing. Here is a Sass implementation of the original underline gist by Adam Schwartz:

@mixin text-underline-crop($background) {
  text-shadow:  .03em 0 $background, 
                  -.03em 0 $background,
                  0 .03em $background,
                  0 -.03em $background,
                  .06em 0 $background,
                  -.06em 0 $background,
                  .09em 0 $background,
                  -.09em 0 $background,
                  .12em 0 $background,
                  -.12em 0 $background,
                  .15em 0 $background,
                  -.15em 0 $background;
}

@mixin text-background($color-bg, $color-text) {
  background-image: linear-gradient($color-text, $color-text);
  background-size: 1px 1px;
  background-repeat: repeat-x;
  background-position:  0% 95%;
}

@mixin text-selection($selection) {
  &::selection {
    @include text-underline-crop($selection);
    background: $selection;
  }

  &::-moz-selection {
  @include text-underline-crop($selection);
  background: $selection;
  }
}

@mixin link-underline($background, $text, $selection){
  @include text-underline-crop($background);
  @include text-background($background, $text);
  @include text-selection($selection);

  color: $text;
  text-decoration: none;

  *,
  *:after,
  &:after,
  *:before,
  &:before {
    text-shadow: none;
  }

  &:visited {
    color: $text;
  }
}

/* Example usage */
a {
  @include link-underline(#fff, #333, #0BF);
}
SmartUnderline is a library that simplifies the process

It is highly recommended to reserve underlines only for hyperlinks. This is a trend that the majority of websites follow, and deviating from it may cause confusion.

Further Readings:

Conclusion

Congratulations for making it to the end of this handbook. Typography on the web — a medium where the user can be on any device in any resolution — is extremely difficult. When I first started designing websites years ago, I found it near impossible to find up-to-date information on best web typographic practices. There were many blog posts by experts with contradicting information, and existing books on the subject of web typography rarely go into details on technical implementation. Typography Handbook aims to solve this problem and provide almost everything that a beginner would need to know to create industry-standard typographic elements. I hope that this goal succeeded with you.

Further Readings:

Version 2.0.0 — Last Updated 10/24/2019 | View on GitHub