Fluid typography and accessibility

By aumlette

December 26, 2020 – 20 minute read

Explore how fluid typography makes your website interesting and responsive whilst always ensuring accessibility to all users is front and centre.

Media queries

Today, with the desire for responsive websites, we are all familiar with media queries. They are used to dictate how the appearance of the page will change based upon the device size.

Fonts can get bigger or smaller. Spacing can increase or decrease. The design moves from a single column to multiple columns. And so on.

min-width or max-width?

In structuring media queries there are two basic choices. Start off with the website for the smallest devices and change things as the device gets bigger - min-width - or start with the largest device and make the changes as things get smaller - max-width.

Which should we use? It doesn't matter; you can use either. There will be absolutely no difference to the end result. The only maxim is to pick one and stick with it. Don't mix them.

We prefer to start small and make changes as things get bigger, so we use min-width. Not so we can claim to be mobile first; that's just a term that people like to throw about when in truth all devices matter so it's no more important to start small or big. No, the reason we prefer to start small is we consider that we are always going to have to cope with the smaller devices. As the device gets bigger we may or may not make a difference at some arbitrary point. In this case we just bolt on an extra min-width condition or two to the end of the list, rather than having to restructure it.


Our preferred breakpoints consider specific sizes, not specific devices. Don't try and set breakpoints based an iPhone this or an Android that. The distinction between portrait and landscape doesn't matter either.

Firstly there are more devices with different screen sizes than you can count and secondly what matters is the available width, not what that width is on. This distinction becomes much more important as we'll discover later on.

Minimum widthDevice size
0pxextra-small (default)

This table shows why we prefer to use min-width. Based on our design, we may or may not include the breakpoint at 1600px. Or we may decide to have another breakpoint at 1920px for example. What we do know, however, is we won't need to make further distinction below 1440px.

px or em?

So, 5 simple breakpoints, cleanly defined using pretty common sizes which will fit well with a large variety of devices. Job done. Actually, no. What we have defined above is wrong. Not just wrong, it's bad. It's not accessible.

One of the things we use media query breakpoints for is to change the layout. For example, once the page becomes at least 1024px wide we might change from a one column to a two column layout. With the extra width there's no need to have everything vertical, and we could put some information in a sidebar.

This appears to work fine, however, it is based upon one major assumption; the base font size for the website. What we might do is set the base font size to 16px as that is a standard size, chosen as the default by most if not all browsers.

What we've forgotten is that the user can change this base font size. Instead of working on a base font size of 16px, maybe they prefer 20px or 24px or 12px. Whatever the reason, it doesn't matter. The point is that it can be changed. They can also zoom in or out with effectively the same effect.

Now everything breaks and gets messed up. The two columns which looked lovely at 1024px look horrible because the font size is 24px instead of 16px. At this font size we need to go back to one column. Having the breakpoints defined in px, however, makes this impossible. The px is an absolute value.

The solution is to use em. The em is a relative unit where 1em is defined to be the base font of the browser. Assuming this remains at its default we can think of 1em being 16px. So now let's redefine our media query breakpoints.

Minimum widthDevice size
0emextra-small (default)

Now, assuming a base font of 16px we will change to our two column layout on large devices which are at least 64 x 16px wide. But 64 x 16px = 1024px. What's the big difference?

The big difference is if user changes the base font size of their browser. Suppose they choose 24px. Now the "large" size with its two column view will only kick in at 64 x 24px = 1536px. At 1024px we will remain in one column, which with the much larger font size, makes sense. Actually 1024px = 42.66em so we are actually still following the "small" rules; we've not even reached the medium breakpoint yet.

em or rem?

It doesn't matter. In our opinion typography is a fundamental core concept that is defined at the start of the website styling. Therefore both rem and em will be defined by the base font size of the website and in the absence of any other reason we'll stick with em.

Responsive typography

We're all used to responsive typography. The font size changes as the device size changes. Typically as the screen gets bigger the fonts get bigger. It makes it our website more readable so no problem there. The normal way to implement this is at the media query breakpoints. For example, we might define some our our typography like this.

html {
  font-size: 100%;

p {
  font-size: 1em;

  @media screen and (min-width: 48em) {
    font-size: 1.125em;
  @media screen and (min-width: 64em) {
    font-size: 1.250em;

h3 {
  font-size: 1.333em;

  @media screen and (min-width: 48em) {
    font-size: 1.500em;
  @media screen and (min-width: 64em) {
    font-size: 1.666em;

h2 {
  font-size: 1.777em;

  @media screen and (min-width: 48em) {
    font-size: 1.999em;
  @media screen and (min-width: 64em) {
    font-size: 2.221em;

h1 {
  font-size: 2.369em;

  @media screen and (min-width: 48em) {
    font-size: 2.665em;
  @media screen and (min-width: 64em) {
    font-size: 2.961em;

Note, the examples in this article have all be written in SCSS. Also, we've applied a visual type scale using a scale of 1.333 (perfect fourth) to give a sensible ratio between the various tags in case you were wondering why some of the numbers look a bit strange.

Anyhow, this is just an extract; we've only defined the p tags plus 3 of the heading tags. Also we've only included a couple of the breakpoints but it's enough to be able to talk about.

Browser base font size: 16px

Device widthp tagh3 tagh2 tagh1 tag
less than 768px16px21.33px28.43px37.90px
768px to 1023px18px23.99px31.98px42.63px
1024px and greater20px26.66px35.54px47.37px

Browser base font size: 24px

Device widthp tagh3 tagh2 tagh1 tag
less than 1152px24px31.99px42.65px56.85px
1152px to 1535px27px35.99px47.98px63.95px
1536px and greater30px39.99px53.31px71.06px

These tables give an idea of how the fonts will be sized for the various tags at different device widths. The key thing to note is the transformation between font sizes will be sudden and abrupt. A p tag displayed at 16px when the browser width is 767px will change instantly to 18px when the browser width becomes 768px.

We'll be honest though, what we have here is already perfectly good. It's responsive. It's accessible. It just maybe doesn't show off too much.

Fluid typography

This article is supposed to be about fluid typography. We've done a lot of talking and haven't even mentioned this yet.

Actually this article is about fluid typography and accessibility. All our talking has been setting the ground rules to ensure we retain accessibility. Fluid typography is a nice to have; accessibility is essential.

Fluid typography is just a fancy way of saying let's get rid of that sudden and abrupt font size change. As the browser window grows or shrinks, so the font size gets bigger and smaller. Smoothly, not just in jerky steps.

Setting the boundaries

Firstly we don't want the font size to grow or shrink unconditionally.

We want lower and upper boundaries, so for the smaller devices it will always be, for example 16px, and for larger devices it will always be 20px. It's just between this minimum and maximum that we will see smooth transitions. These boundaries were originally coined CSS locks by Tim Brown back in 2016.

Using our example set of breakpoints above, we've decided that we only want our typography to be fluid for small, medium and large devices. So our boundaries are set as 36em and 64em respectively. Note, again, our boundaries are defined in em. That is absolutely crucial to maintain accessibility.

Making the calculation

With the boundaries are in place, all we need is a little function to vary the font size. Basically at the lower boundary the font size is the minimum acceptable, and at the upper boundary it's the maximum. In between it's simply a percentage between the two, depending on where we are exactly within our boundaries.

Using SASS for our styling makes this a simple job with a little function.

$lower-boundary: 36em;
$upper-boundary: 64em;

@function fluid-typography-font-size($min, $max) {
  $vw-multiplier: ($max - $min) / ($upper-boundary - $lower-boundary);
  $fixed-constant: $min - $vw-multiplier * $lower-boundary;

  @return calc(#{$vw-multiplier * 100}vw + #{$fixed-constant});

The font size becomes a ratio between a minimum and maximum (1em to 1.25em, for example) against how wide the browser viewing window is when it's between the boundaries (36em and 64em).

Bounding the font size

Now we simply want our font size calculation to take place when we're in that zone between the lower and upper boundaries. Again, we use the convenience of SASS to define a mixin.

@mixin fluid-typography($min-value, $max-value) {
  & {
    font-size: $min-value;

    @media screen and (min-width: $lower-boundary) {
      font-size: fluid-typography-font-size($min-value, $max-value);
    @media screen and (min-width: $upper-boundary) {
      font-size: $max-value;

By default we set the font size to its minimum value. When we reach the lower boundary a media query applies the fluid calculation function. And when we reach the upper boundary we override this by the maximum value.

Typography made simple

With the mixin in place we can simplify our typography to just one line for each tag.

p {
  @include fluid-typography(1em, 1.25em);

h3 {
  @include fluid-typography(1.333em, 1.666em);

h2 {
  @include fluid-typography(1.777em, 2.221em);

h1 {
  @include fluid-typography(2.369em, 2.961em);

Below the lower boundary the minimum font size is applied. At and above the upper boundary the maximum font size is applied. In between it's a smooth, fluid transition between the two.

All done in em. Not a single px value in sight. Everything based on the browser base font.

Everything completely accessible.


Leave a comment

Cancel reply
Let's talk

Need help telling your story?

It all starts with a conversation.