Fluid typography with CSS clamp

Categories

Learn to create a simple, accessibility friendly and configurable fluid type system that uses modern CSS sizing functions.


I’m a big fan of clamp()—it’s decent at doing what I like to do the most with CSS: let the browser do its job with some hints at how to do it. It also provides just the right amount of control, which is handy for layout elements, too.

In this tutorial, we’re going to use clamp to generate a little fluid type system that can be configured using CSS Custom Properties.

Getting started permalink

All we need for this tutorial is a little HTML page and a CSS file. Go ahead and create the following files:

  1. index.html
  2. global.css

Now, inside index.html, add the following:

Code language
HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Fluid type demo</title>
    <link rel="stylesheet" href="global.css" />
  </head>
  <body>
    <article class="[ post ] [ flow ]">
      <h1>Fusce dapibus, tellus ac cursus commodo</h1>
      <h2>Donec ullamcorper nulla non</h2>
      <h3>Morbi leo risus, porta ac consectetur</h3>
      <p>Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Donec sed odio dui. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper.</p>
    </article>
  </body>
</html>

That’s it for HTML. It’s some lipsum placeholder text to demonstrate our fluid type system. Job done!

Digging in to the CSS permalink

Now for the fun part. Let’s add our basic global styles first. Open up global.css and add the following to it:

Code language
CSS
body {
  background: #f3f3f3;
  color: #252525;
  line-height: 1.5;
  font-family: Georgia, serif;
  padding: 2rem;
}

h1,
h2,
h3 {
  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue,
    helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif;
  line-height: 1.1;
  font-weight: 900;
}

That’ll make things look a little nicer. We’re using my favourite font, Georgia, as our base and the system font stack for headings. This contrast will really help you see the fluid type in action.

Now we can add the smart stuff—the fluid type setup. Open up global.css and add the following to it:

Code language
CSS
h1,
h2,
h3,
p {
  font-size: clamp(
    var(--fluid-type-min, 1rem),
    calc(1rem + var(--fluid-type-target, 3vw)),
    var(--fluid-type-max, 1.3rem)
  );
}

There’s a lot going on here, so let’s break it down.

The clamp() function takes a minimum value, an ideal value and a maximum value. This allows us to create some locks.

To power all of this, we’re using 3 custom properties:

  1. --fluid-type-min is the smallest we will allow our text to go
  2. --fluid-type-target is our ideal, fluid setting. We use calc() because if you just use a viewport unit to size your type, it can cause problems in zooming, which in turn, creates a WCAG accessibility failure.
  3. --fluid-type-max is the largest we will allow our text to go

For all three custom properties, we are setting the default value as the second parameter. This means that you can drop this fluid type system into any project, and even if none of those properties are defined: the system will still work off those default, sensible values.

It’s really important to apply fluid type responsibly. Luckily for us all, Adrian Roselli has written about this in depth. The specific criterion is here.

It’s really important to test that your text gets large enough when you zoom in and small enough when you zoom out—it should be very obviously larger or smaller. Because we’re using a rem as part of our fluid calculation in this tutorial, we’re helping that, considerably.

Implementing our system permalink

We have applied our fluid type system to the following elements: h1, h2, h3, p. We could—if we wanted—turn this into a utility class for maximum portability. For this tutorial, we’ll keep it simple with type selectors though.

We want to add some specific settings for each of these, using custom properties, or all the text will be the same size.

Open up global.css and add the following to it:

Code language
CSS
h1 {
  --fluid-type-min: 2.5rem;
  --fluid-type-max: 5rem;
  --fluid-type-target: 5vw;

  max-width: 15ch;
}

h2 {
  --fluid-type-min: 1.8rem;
  --fluid-type-max: 3rem;
}

h3 {
  --fluid-type-min: 1.5rem;
  --fluid-type-max: 2.5rem;
}

h2,
h3 {
  max-width: 30ch;
}

p {
  max-width: 60ch;
}

For the <h1>, we increase the --fluid-type-target to a larger, 5vw. By increasing the viewport unit, we speed up the rate of growth, which will help to maintain its extra large size. To reduce the rate of growth and have less difference between your minimum and maximum sizes: reduce the size of --fluid-type-target.

For all of the other elements, the default growth rate is fine, so all we’re doing is setting sensible minimum and maximum sizes, using standard rem units.

We’re limiting the width of all of the elements, using a ch unit to improve readability. You can read more about that here.

We’re done! You can see a live demo of what we have built, here. You can also download the completed source files, here.

Wrapping up permalink

This is a very simple, bare-bones system and will comfortably support a lot of usecases. For more advanced, complex designs, I would recommend using something like Typetura which gives very fine control or Utopia, which is a level up from this approach that we’ve learned today.

For full disclosure, I’m not a huge fan of fluid type, personally. I’ve had such a mixed, checkered history with it over the years, and personally, I prefer to create a size scale, implemented via media queries. That’s what this site does, at the time of writing.

Fluid type is in demand, too, so what you’ve learned today will undoubtedly be useful at some point—if nothing else, to give you a useful context of how clamp() works.

Until next time, take it easy 👋


Big thanks to Eric Bailey for casting his expert eye on this for me.


Comments

If you liked this post, you might like these ones, too

  1. Make a button element look like a link

    🔥 A handy quick tip.

    Continue
  2. CSS Logical Properties

    Create spacing that works regardless of the direction of your content or the environment of your users.

    Continue
  3. Two simple methods to vertically and horizontally center content with CSS

    Continue

Become a supporter by joining the Piccalilli Membership

For $5 per month, you get access to a private, friendly Discord community, a regular newsletter, huge discounts on courses and free access to all premium tutorials.

Most importantly, by becoming a supporter, you help make as much content, free-to-everyone as possible on this site, which benefits everyone. As a member, you also get an ad-free experience around the site.

Support Piccalilli by becoming a member

Sign up for updates

Stay up to date with updates from Piccalilli. You’ll get alerted as soon as any new content gets published. You’ll also get updates on upcoming courses and membership features! You can unsubscribe at any time, too.