In CSS, there are many different units that can be used to size elements on a page—px, vw, ch, em, rem, and far too many others to list here. Of all these units, rem is the most reliable for font sizing, allowing you to style text responsively so that it scales whenever users change their preferred browser font size. Let's understand why this matters.

Table of Contents

Don't Use Pixels for Font Size

The traditional unit for sizing anything on the web is the CSS pixel, but it's not ideal for font size. While pixels do make it easy for you to translate mockups from design software directly into CSS, they're an absolute-length unit, which means that one CSS pixel corresponds to a fixed physical pixel size (device pixel) on a user's screen.

CSS pixels are the easiest unit to understand because they're grounded in physical measurements. But like other absolute units, they don't scale—15px will always be 15px on the same device. Using pixels is a particularly bad practice for font sizing because it can create some accessibility problems for users with vision impairments. To understand why, we need to learn about user font size preferences.

User Font Size Preferences

Every browser applies a root font size of 16px to a document, meaning that unstyled body text will have a rendered font size of 16 CSS pixels if you don't apply any custom styling to the page. However, both developers and users can alter this behavior. Developers can change the font size of the root element (html) with CSS so that all elements inherit that new font size:

html {
  /* This is purely illustrative; don't do this! */
  font-size: 18px;
}

Likewise, users can go into their browser settings (e.g., chrome://settings/fonts in Chrome) and specify their preferred font size:

The Chrome settings page for changing one's preferred font size. Two sliders can be seen: one for the font size and another for the minimum font size. Sample sentences are shown below those sliders, along with pickers for the user's preferred font family (which, by default, is Times New Roman).

The bottom line is that user preferences for font size should always take precedence over your CSS—you should never make assumptions about how your users will view your page. Using hard-coded pixels for font size is inaccessible to users with vision impairments because they may need to scale up the font size of your page to read the text more easily. But when you set a font size in pixels like we did in the example above, the text will always render at that size, regardless of what font size a user prefers. You can learn more about why this matters in WCAG Criterion 1.4.4 Resize Text.

Preferred Font Size vs. Browser Zoom Level

Users can also zoom in all web pages globally in their browser settings, in which case it seems like pixels are not entirely problematic because the page still scales up proportionally.

The Chrome settings page shows various groups, one of which is labeled page zoom. There's a dropdown input, with a currently selected value of 100%.

For example, changing the root font size from 16px to 18px is equivalent to setting the page zoom to be 112.5%. So you might be tempted to use pixels for all of your font sizes and leave it up to users to zoom in your page if they have trouble reading the text.

However, more commonly, users have a preferred font size for their monitor rather than a preferred zoom percentage—after all, it's much easier for users to reason about pixels than it is some arbitrary percentage zoom. Moreover, as in the example above, percentages may yield floating-point results that don't appear in the browser settings, forcing a user to choose between the two closest values that are available.

This means that we should always respect the user's preferred font size rather than forcing them to figure out whether we support zooming only, font size scaling only, or both zooming and font size scaling.

If you're wondering why this matters so much and why we can't just use pixels and let users zoom in the page if they really want bigger text, Kathleen McMahon did a deep dive into why rems are better for accessible font sizing back in 2019.

Relative Units and Font Size

Absolute units like pixels aren't ideal if we want to respect our users' font size preferences. Fortunately, CSS also offers relative-length units, which reference other elements on the page rather than using absolute values. Three such units are percentages, ems, and rems. Since percentages and ems behave the same in the context of font sizing, we'll look at just ems and compare them to rems.

A First Attempt with em

Consider the following HTML:

<div class="parent">
  <div class="child"></div>
</div>

For the font-size property, 1em is relative to the font size of that element's parent. So if the parent element has a font size of 24px, a child with a font size of 1em would get a computed font size of 24px. Likewise, 0.5em would compute to 12px:

.parent {
  /* I'm using pixels for illustrative purposes. */
  font-size: 24px;
}
.child {
  /* 0.5 * 24px = 12px */
  font-size: 0.5em;
}

Unfortunately, em is problematic for font sizing. If you give one element a font size in ems and a child a font size also in ems, those two values will create a compounding effect. For example, suppose we now introduce another element as a child of the child and give that element an em-based font size:

<div class="parent">
  <div class="child">
    <div class="deeply-nested"></div>
  </div>
</div>
.parent {
  /* I'm using pixels for illustrative purposes. */
  font-size: 24px;
}
.child {
  /* 0.5 * 24px = 12px */
  font-size: 0.5em;
}
.deeply-nested {
  /* 0.5 * 0.5 * 24px = 6px */
  font-size: 0.5em;
}

For this reason, ems are not recommended for font sizing because an element may be nested arbitrarily deep in the DOM. Thus, an element's em-based font size cannot be determined reliably just by looking at its own CSS—it depends on where that element is inserted into the tree. This makes it very difficult to create independent, reusable components.

Responsive Font Sizing with rem

By contrast, rem (which stands for "root em") always references the root font size of the document as its single source of truth. Assuming that the root font size is 16px (as is the case in all modern browsers), we get the following values:

  • 1rem = 16px
  • 1.5rem = 24px
  • 0.5rem = 8px

The great thing about rem is that it's predictable: we can safely use it for font sizes in nested layouts and component frameworks since it always references the root font size rather than some arbitrary ancestor font size. This makes rem the ideal unit for font sizing, allowing us to define font sizes in responsive units that respect user preferences. So if a user changes their preferred font size in their browser settings, all of your rem-based sizes will scale accordingly and use the new value as their basis. For example, let's update the code sample from earlier to use rems:

.parent {
  /* 1.5 * 16px = 24px */
  font-size: 1.5rem;
}
.child {
  /* 0.5 * 16px = 8px */
  font-size: 0.5rem;
}
.deeply-nested {
  /* 0.5 * 16px = 8px */
  font-size: 0.5rem;
}

Now, suppose the user changes their preferred font size to 18 in their browser settings. Effectively, this scales up the parent and child font sizes (independently): 1.5rem computes to a font size of 27px, and 0.5rem computes to 9px. Conversely, if a user were to decrease their preferred font size, our font sizes would decrease proportionally (but still independently). This allows us to design our app for the "normal" use case of browsers with a root font size of 16px without locking users into a hardcoded font size—they're free to change it as they please.

Rems Made Easy

At first, it may take you a bit of practice to get the hang of expressing numbers in rems if you're not used to it. But with time, you'll find that it's actually quite easy, especially if you like to work with powers of two. For example, 8px in rems is just half of 16px, so we know it's 0.5rem. Likewise, 4px is 0.25rem, 2px is 0.125rem, and so on.

However, in practice, it's unlikely that you'll want to pull random values out of thin air and hardcode them in your styles. This may seem convenient in the short term, but it makes it harder to maintain your CSS in the long term. Instead, it's better to define custom properties upfront for all of your desired sizes using a simple scale (known as a type scale):

html {
  --font-size-300: 0.75rem; /* 12px */
  --font-size-400: 1rem;    /* 16px, base */
  --font-size-500: 1.25rem; /* 20px */
  --font-size-600: 1.5rem;  /* 24px */
  --font-size-700: 1.75rem; /* 28px */
  --font-size-800: 2rem;    /* 32px */
  --font-size-900: 2.25rem; /* 36px */
  /* etc. */
}

Now, instead of hardcoding font sizes in your CSS, you can reference these variables:

.element {
  font-size: var(--font-size-400);
}

Note that this is just one convention. Here's another example:

html {
  --font-size-sm: 0.75rem;   /* 12px */
  --font-size-base: 1rem;    /* 16px, base */
  --font-size-md: 1.25rem;   /* 20px */
  --font-size-lg: 1.5rem;    /* 24px */
  --font-size-xl: 1.75rem;   /* 28px */
  --font-size-xxl: 2rem;     /* 32px */
  --font-size-xxxl: 2.25rem; /* 36px */
  /* etc. */
}

The nice thing about using CSS variables is that you don't have to constantly translate pixels to rems on the fly as you implement a design. Rather, you can reference a predefined set of variables and trust that the values correspond to your design system tokens under the hood. You could even ask your designers to use the same naming convention as you do in your code so that you don't have to look up the raw pixel value used in designs—you can just copy the name of the font size variable.

Finally, note that if you do need to convert rems to pixels in one-off situations, you can:

Final Thoughts

Pixels are the most popular unit for sizing elements on the web, but they're not great for font sizing because they lock users into just that size, preventing them from scaling the text on the page except by zooming. Instead, it's recommended that you set your font sizes in rems to respect user preferences.

Attributions

Social media preview: Photo by Chrissie Giannakoudi (Unsplash).