Skip to main content

Developing a Typography Boilerplate

Building a boilerplate that focuses on typography is an easy way to style the majority of a website or application’s content with relatively little effort, leading to a better final product.

Blank pages can be intimidating, whether they’re empty CSS files or empty art boards in Figma. When you don’t already know what you’re building, how can you decide how to build it? Well, you can start by determining what you’ll need for sure, and preparing for those things first. For example, every website will have typography, like the paragraph you’re reading right now!

Every project, website, or design system is unique and special, but you can cover 80% of the typography needs for most things by covering a handful of sizes and visual treatments. If we’re aiming for boilerplate that’s useful without getting in the way, 80% is good enough.

Font Families

For performance and brand unity reasons, it’s best to limit the number of fonts used on any given site. It’s not uncommon to have both serif and sans-serif fonts to distinguish between things like body copy and headings, so our boilerplate will include both. You may have big, attention-grabbing text that would benefit from a more stylized font, so include one and call it the display font. And if you have a site that has lots of code samples or content that benefits from a monospace font, you can include one of those as well.

For a CSS boilerplate, you could use keywords like serif or monospace until designers choose the actual fonts. To have things look a little better in the meantime without needing to futz with hosting font files or making @font-face declarations, you can start with some nice system fonts. Depending on your project, you may also want to namespace your tokens to avoid collisions with third-party code. Since this is meant to be copied into any new project, we’ll use a placeholder NAMESPACE that will be easy to find and either replace or delete.

/* styles/tokens/typography.css */
:root {
  --NAMESPACE-font-family-display: Bahnschrift, 'DIN Alternate', 'Franklin Gothic Medium', 'Nimbus Sans Narrow', sans-serif-condensed, sans-serif;
  --NAMESPACE-font-family-sans: Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif;
  --NAMESPACE-font-family-serif: Rockwell, 'Rockwell Nova', 'Roboto Slab', 'DejaVu Serif', 'Sitka Small', serif;
  --NAMESPACE-font-family-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
}

Font Sizes

Body copy is a given, but there are often times where text needs to be a little larger to draw attention or a little smaller to signal lesser importance. Having a few options for small, medium, and large body copy makes sense. Display sizes are a little more rare, but it doesn’t hurt to include them, just in case. There are also six heading level tags (h1-h6) to account for, so it makes sense to cover those, even if the h5 and h6 never see the light of day.

/* styles/tokens/typography.css */
:root {
  --NAMESPACE-font-size-display-lg: 7.25rem;
  --NAMESPACE-font-size-display-md: 5.5rem;
  --NAMESPACE-font-size-display-sm: 4rem;
  --NAMESPACE-font-size-heading-xxl: 3rem;
  --NAMESPACE-font-size-heading-xl: 2.25rem;
  --NAMESPACE-font-size-heading-lg: 1.75rem;
  --NAMESPACE-font-size-heading-md: 1.25rem;
  --NAMESPACE-font-size-heading-sm: 1rem;
  --NAMESPACE-font-size-heading-xs: 0.875rem;
  --NAMESPACE-font-size-body-lg: 1.25rem;
  --NAMESPACE-font-size-body-md: 1rem;
  --NAMESPACE-font-size-body-sm: 0.875rem;
}

The values I’ve chosen here are somewhat arbitrary, but it doesn’t matter because designers are likely to choose different values. What’s more important is the naming. I chose T-shirt sizes for two reasons:

  1. By not naming heading sizes after tag names, like h1, we’re less likely to break semantics for the purposes of styling.

  2. Using T-shirt size naming discourages adding unnecessary levels and helps streamline the design when possible.

This may not work if you’re working on an enterprise design system that needs to scale and adapt to many different branding needs, but it’s good enough to cover the basics for most projects.

What About Using Fluid Font Sizes?

Quite often, we want our font sizes to be smaller on mobile devices and larger on things like desktops or tablets. This boilerplate does not account for this because there are so many different approaches that may be preferable for different projects.

You could opt to use clamp() with either viewport or container units to smoothly increase the font size between minimum and maximum values, but there are some drawbacks to consider. Depending on how you scale the font size, there may be cases where the text cannot scale up 200%, failing WCAG SC 1.4.4. There are ways to mitigate this problem, but you’d end up using a lot of math or magic numbers, and it’s hard to determine what the font-size should actually be at any given screen size.

Another option would be to adjust the font sizes based on media queries or container queries. This offers more control and is likely to avoid the magic number problems, but the sudden changes can have knock-on effects on layouts and such, forcing you to adjust more than just typography as those breakpoints take effect.

The important thing is to clarify what the intended behavior should be with designers. If they’re intending for the font sizes to grow linearly as the screen size gets larger, but developers implement media queries with sudden changes, that’s probably going to lead to other inconsistencies and miscommunications.

Creating a test page

To cover as many typographical needs as possible, I’d recommend creating a test page with content representative of all the ways text can be used on any given site. The following elements should be included:

  • Headings (h1-h6)

  • Paragraphs (p)

  • Basic lists (ul, ol, each nested three levels deep)

  • Description lists (dl, dt, and dd)

  • Inline quotes (q and/or regular quotation marks)

  • Block quotes (blockquote and cite, marked up with figure and figcaption because block quotes are complicated)

  • Links, especially inline links in paragraphs (a)

  • Strong and emphasis tags (strong and em)

Depending on the type of content you expect, you may also want to cover more niche uses of typography, like pre, code, sub, sup, or other inline elements like small.

You can use these elements to build a test page full of content that describes how different typographical elements should be used. This page serves multiple purposes: it documents the project’s approach to typography and provides an easy way to review how changes to typographical styling affect the site. While building this test page, you will also need to solve for things like maximum line length, line-height, and the top and bottom margins for block elements like headings and paragraphs, which will let you establish the vertical rhythm of content early in the project.

A screenshot of a test page with three different heading levels and paragraphs demonstrating line lengths and spacing between text.
A good test page serves as example and documentation at the same time.

Low specificity selectors with broad reach can be super helpful for this sort of thing, and you can build them into your CSS reset or base typographical styles. For example, this snippet will ensure that block typographical elements will have reasonable line-heights and vertical spacing, regardless of font-size.

:where(h1, h2, h3, h4, h5, h6, p, ul, ol, dl, dt, figure, blockquote) {
  line-height: calc(1em + 0.5rem);
  
  &:not(:first-child) {
    margin-block-start: 1em;
  }
}

It’s important that this page is built using raw HTML elements with no class names, since that is the most likely output from rich text editors and content management systems. By focusing on the defaults, we can ensure that content looks good. Then, in cases where some additional tweaks are needed, we can enhance from there. We can also create aliases that mimic default styling for elements, so if you need a semantic h3 to be smaller, you can use .heading-sm instead of inappropriately using an h5.

:where(h1, h2, h3, h4, h5, h6),
:is(.heading-xxl, .heading-xl, .heading-lg, .heading-md, .heading-sm, .heading-xs) {
  font-family: var(--NAMESPACE-font-family-serif);
  font-weight: 700;
}

:where(h1),
.heading-xxl {
  font-size: var(--NAMESPACE-font-size-heading-xxl);
}

:where(h2),
.heading-xl {
 font-size: var(--NAMESPACE-font-size-heading-xl);
}

:where(h3),
.heading-lg {
  font-size: var(--NAMESPACE-font-size-heading-lg);
}

:where(h4),
.heading-md {
  font-size: var(--NAMESPACE-font-size-heading-md);
}

:where(h5),
.heading-sm {
  font-size: var(--NAMESPACE-font-size-heading-sm);
}

:where(h6),
.heading-xs {
  font-size: var(--NAMESPACE-font-size-heading-xs);
}

“Boring” content first, more exciting components later

It’s not at all unusual for a new project to start with designing the home page. After all, that’s what users will see first, and it’s an introduction to the brand, so it’s a logical starting point. But most sites are made up of more visually boring, content-heavy pages that aren’t going to be able to support the same components we make for flashy marketing pages. It’s also hard to translate a home page design (big text! fancy animations! calls to action!) into “regular” content.

By flipping the order, we can spend 20% (or less) of our effort to support 80% (or more) of the content right away. The outcomes from determining ideal font families, sizes, weights, etc., will better inform those flashier pages and components that are more exciting to work on. Working out these details with designers early on means more cohesive sites and fewer last-minute adjustments for unexpected content.

You don’t even need to have a repository set up to get started. A basic CodePen project like this one that I set up is a fine starting point for collaborating with designers at the beginning of a project. Then, once the basics are more or less “baked,” you can copy and paste as needed into the actual project’s code.

A good boilerplate is a strong foundation

Content is king, and typography is a significant part of brand identity and design. Using boilerplate to quickly establish a baseline will make it that much easier to build on that foundation. By having a handful of tokens and a test page, it’s even easier to make small changes and see how they will affect the project. Then you can move on to all the other little details that make up the rest of the design.

Want to talk about how we can work together?

Katie can help

A portrait of Vice President of Business Development, Katie Jennings.

Katie Jennings

Vice President of Business Development