Accessibility and font sizes

It’s now 2022, and I’m still seeing far too many websites using static pixel sizes for fonts. Even big hitters like Facebook do this.

Stop it!

Using the wrong units of measurement in your Cascading Style Sheets (CSS) is a big barrier for many visually impaired users, and it can cause your website fail the Web Content Accessibility Guidelines (WCAG) 2.1 on 1.4.4 Resize text.

All modern browsers allow people to override the default font size in the user settings. This is part of the User Agent Accessibility Guidelines (UAAG). UAAG is similar to WCAG, but it focuses on the tool being used to view the website, rather than the website itself.

You can try it. If you’re using chrome and you type chrome://settings into the address bar, then search for ‘font size’, you will see that you can set it to various sizes. However, depending on the websites you’re browsing, you might not actually see it work.

The reason it might look like a broken setting on some websites is that the browser gets its initial instructions from the user settings, but it gets the rest of its intructions from the website you’re viewing.

The browser will always prioritise the website CSS. If it didn’t, all websites would look the same. This means developers have a responsibility to code websites properly. If the website is coded badly, then it will override the useful settings in the browser and make it inaccessible for some users.

The problem with pixels

Pixels are a static unit of distance. Ok, they’re not truly static like metres or miles, as a pixel can technically be made bigger or smaller by changing the resolution of the screen. But, because people don’t tend to alter their resolution on a website per website basis, we can think of them as being static. 1px will always be 1 dot on the screen you’re currently using.

So, if you set the font size to be 16px in your CSS, it is always going to be 16 physical dots high on the screen, no matter what you change in the user settings. The browser will literally just count 16 pixels high and then draw the font to that size. This makes the site not respond to the settings the user has specified and may make it inaccessible for people who cannot read small fonts.

Relative font sizes

To allow the fonts and the spacing around it to scale to the user settings, you should use relative units of measurement such as REM, EM or percent.

Percent does exactly what you would expect. It increases or decreases the current font size by a given percentage. So, 200% would be double the current font size. Easy, right?

REM and EM are a bit more ambiguous. You’d think that EM was an acronym or an abbreviation, but it is not. An EM is a unit of measurement which made sense in the old world of physical printing, but when it got ported over into the digital world, it doesn’t really make sense anymore.

What is EM?

If you’re a bit of a nerd, like me, and you want to know what it means; 1em comes from the width of the capital ‘M’ in traditional printing.

Font size was determined by the physical size of solid metal cast letter punches. The M was typically the widest letter available in a set, and was therefore useful for measuring line heights and margins. So, if you had a line height of 1em, the gap between lines of text would be the same as the width of the letter M in that font.

I’m not entirely sure why the unit is ‘EM’ and not just ‘M’. I imagine it was just how it happened to be transcribed when somebody eventually decided to write it down. Or maybe it’s because ‘M’ was already being used for metres. I guess it would really mess your print up if you mistakenly made your font 1 metre high instead of 1em high!

How does EM work?

EM, like percent, will inherit its size from its parent element. So, the EM integrates quite well in the digital world through the use of cascading styles.

For example, if you wanted a child element to have a font size twice as large as the parent element, you can set the child to 2em.

For the purposes of this example, we will set the font size in pixels, just so it is clearer what is going on when we do the math. But, in reality, we’d avoid the use of pixels all together for the reasons I have already mentioned.

Example of EM font size:

body { font-size: 16px; }
h1 { font-size: 2em; }
p { font-size: 1em; }
<body>
  <!-- font size of the body is set to 16px -->
  <h1>At 2em, this heading will be 32px: 2(em)*16(px)=32(px)</h1>
  <p>At 1em, this paragraph text will be 16px: 1(em)*16(px)=16(px)</p>
</body>

The problem with EM and Percent

EM and percent suffer from a problem called compounding. Because they inherit from their parents, compounding happens when font sizes are unexpectedly calculated over multiple nested levels. The font size is recalculated with every nested child element giving you a much larger or smaller font size than originally intended.

For example:

body { font-size: 16px; }
.parent { font-size: 1em; }
.child { font-size: 2em; }
<body>
  <!-- font size of the body is set to 16px -->
  <div class="parent">
    This text would be 16px: 1(em)*16(px)=16(px)
    <div class="child">
      This text would be 32px, as expected: 2(em)*16(px)=32(px)
      <div class="child">
        This text would be 64px, not as expected: 2(em)*32(px)=64(px)
        <div class="child">
          This text would be 128px, definitely not as expected: 2(em)*64(px)=128(px)
        </div>
      </div>
    </div>
  </div>
</body>

To get around this problem of compounding, we can use the REM unit.

What is REM?

Using REMs is the easiest way to make your font sizes scale and still maintain control over your whole design.

REM units are almost the same as the EM unit. The key difference is that rather than inheriting their size from their parent elements, they always inherit from the root element. REM literally means ‘Root EM’, but unlike the old-school EM unit, REMs were invented purely for the digital world, to prevent the compounding issue.

The way a REM works is by always going to the root element on the page to calculate the font size. The root element is the first element on the page, which is the <html> tag, and unless you override it in your CSS, the HTML tag will always try to inherit its default font size from the browsers user settings.

In most modern browsers, the default user setting for font size is 16px. In Chrome and Edge this is labelled ‘medium’, but there are also settings for ‘large’ and ‘very large’. Large is 20px, and very large is 24px. So, font-size:1rem can technically be 16px, 20px or 24px high depending on the user settings in the browser. Whereas font-size:16px will always be 16px.

For example:

p { font-size: 1rem; }  /* could be 16px, 20px or 24px */
p { font-size: 2rem; }  /* could be 32px, 40px or 48px */
p { font-size: .5rem; } /* could be 8px, 10px or 12px */
p { font-size: 16px; }  /* would only ever be 16px */

By using REM, you can nest child elements and the font size no longer has the compounding issue:

.parent { font-size: 1rem; }
.child { font-size: 2rem; }
<html>
  <!-- assume the font size is set to 16px in the user settings -->
  <body>
    <div class="parent">
      This text would be 16px: 1(rem)*16(px)=16(px)
      <div class="child">
        This text would be 32px, as expected: 2(rem)*16(px)=32(px)
        <div class="child">
          This text would be 32px, as expected: 2(rem)*16(px)=32(px)
          <div class="child">
            This text would be 32px, as expected: 2(rem)*16(px)=32(px)
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

Conclusion

Don’t use pixels for font sizes because it removes important accessibility options people may rely on.

Pixel perfect websites are a fallacy. Websites should be fluid. You can never design a perfect layout for every device in the world, so just design a one which works!

You may do a pixel perfect design for mobile, tablet and desktop, but are you really going to start doing designs for people browsing your website on Smart TV’s, Tesla Cars and Smart Watches?

People don’t care if the margin is 3px smaller than the designer wanted it to be. People care when they can’t read the content because you’ve locked down the font size or it overlaps when they scale it up.

As a general rule, just use REM for anything which needs to be flexible and may affect readability, such as font size and margins. Use percent for container widths, such as columns. And, only use EM when your design specifically needs the scaling to be relevant to the parent element. Although, I’m yet to find a reason to ever use EM over REM, but maybe one exists.

Only ever use pixels for stylistic choices which do not need to scale, like borders and horizontal rules. Sometimes you might also want padding in pixels, depending on the design.

For example:

.panel {
  width: 100%;
  font-size: 1rem;
  margin-top: 1.5rem;
  margin-bottom: 1.5rem;
  padding: 10px;
  border: 2px solid #000;
}

And, finally. Always, always; scale the font size up and make sure your design works before you publish it!