DH
David Hellmann
2021/05/07

TailwindCSS: Fluid typography with CSS Clamp

Wait, a TailwindCSS article? Of course, I just had to jump on the hype train. But seriously. I was skeptical for a long time and still am partly. But since a few weeks I'm playing around with it a bit more and I plan to use it more intensively in the future. But that should not be the topic now so quickly back to the headline: Fluid Typography with CSS Clamp.

One point that bothers me about TailwindCSS is how to handle font sizes. In sum, there are just too many jumps that I have to remember, that a team has to remember. Here you quickly get a problem with consistency. Breakpoint set? Breakpoint correct? Sizes right? There are many sources of error.

But fortunately there are ways to simplify the whole thing significantly. Since IE11 support is disappearing more and more as a requirement for projects you can use CSS features like Clamp (Can i use). A pretty powerful CSS property that fits perfectly for this purpose.

It looks like this

      
        /* CSS Clamp */
/* clamp(MIN_VALUE, FLUID_VALUE, MAX_VALUE) */

.sample {
  font-size: clamp(18px, 2vw, 24px); 
}
      
    

Integrating the whole thing into TailwindCSS is a bit more complicated. The FLUID_VALUE is a more complex calculation where you need more values from the TailwindCSS config. Let's have a look at the whole thing in detail.

tailwind.settings.js

This is a part of my TailwindCSS Config where I store some global stuff. In our case the typography and screens stuff is relevant.

      
        /*
 * Tailwind Settings
 */

module.exports = {
  typography: {
    fontSizeMin: 1.125,
    fontSizeMax: 1.25,
    msFactorMin: 1.125,
    msFactorMax: 1.2,
    lineHeight: 1.6,
  },
  screensRem: {
    min: 20,
    sm: 40,
    md: 48,
    lg: 64,
    xl: 80,
    '2xl': 96,
  },
  grid: {
    cols: 24,
  },
}

      
    

tailwind.settings.screens.js

Here I get my breakpoint values from the tailwind.settings.js file, which are stored there, without unit, as REM values and then convert them to pixels, since I use the breakpoints within TailwindCSS with pixel values.

      
        /*
 * Tailwind Screens Settings
 */

const settings = require('./tailwind.settings')

const remToPx = (rem) => {
  return `${rem * 16}px`
}

module.exports = {
  sm: remToPx(settings.screensRem.sm),
  md: remToPx(settings.screensRem.md),
  lg: remToPx(settings.screensRem.lg),
  xl: remToPx(settings.screensRem.xl),
  '2xl': remToPx(settings.screensRem['2xl']),
}

      
    

tailwind.settings.fontSizes.js

Let's move on to the most exciting config file. In tailwind.settings.fontSizes.js file we assemble our clamp property. First we get some settings from tailwind.config.js and furthermore there are two helper functions.

One is calcMulti where we simply calculate the min font size and the max font size depending on our multiplier. The whole thing happens without units and in REM.

And the second helper function clamp assembles our clamp property. Details about this calculation can be found here: Fluid-responsive font-size calculator where everything is explained quite well.

      
        /*
 * Tailwind Font Size Settings
 */

const settings = require('./tailwind.settings')
const fsMin = settings.typography.fontSizeMin
const fsMax = settings.typography.fontSizeMax
const msFactorMin = settings.typography.msFactorMin
const msFactorMax = settings.typography.msFactorMax
const screenMin = settings.screensRem.min
const screenMax = settings.screensRem['2xl']

// Calc Min and Max Fontsize
const calcMulti = (multiMin = 0, multiMax = null) => {
  return {
    fsMin: fsMin * Math.pow(msFactorMin, multiMin),
    fsMax: fsMax * Math.pow(msFactorMax, multiMax || multiMin),
  }
}

// build the clamp property
const clamp = (multiMin = 0, multiMax = null) => {
  const _calcMulti = calcMulti(multiMin, multiMax || multiMin)
  const _fsMin = _calcMulti.fsMin
  const _fsMax = _calcMulti.fsMax
  return `clamp(${_fsMin}rem, calc(${_fsMin}rem + (${_fsMax} - ${_fsMin}) * ((100vw - ${screenMin}rem) / (${screenMax} - ${screenMin}))), ${_fsMax}rem)`
}

module.exports = {
  xs: clamp(-2),
  sm: clamp(-1),
  base: clamp(0),
  lg: clamp(1),
  xl: clamp(2),
  '2xl': clamp(3),
  '3xl': clamp(4),
  '4xl': clamp(5),
  '5xl': clamp(6),
  '6xl': clamp(7),
  '7xl': clamp(8),
  '8xl': clamp(9),
  '9xl': clamp(10),
}

      
    

tailwind.config.js

Our tailwind.config.js then looks like this (shortened version)

      
        /*
 * TailwindCSS Config
 * */

// Settings
const settingsScreens = require('./tailwind.settings.screens')
const settingsFontSizes = require('./tailwind.settings.fontSizes')


module.exports = {
  ...
  theme: {
    ...
    screens: settingsScreens,
    fontSize: settingsFontSizes,
    ...
  },
}

      
    

Final output

If we look at the whole thing in the DEV tools in the browser we see the following line of code for our body font (.text-base).This means nothing else than that our body font has a minimum value of: 1.125rem (18px) and a maximum value of 1.25rem (20px). Everything in between is fluid. The minimum value in our example starts at 20rem (320px) and the maximum value is reached at 96rem (1536px). That means everything in between is controlled by the middle value. No more breakpoints needed. Love it!

The example .text-5xl shows the whole thing with the multiplier 6. In this case this is my H1.

      
        .text-base {
  font-size: clamp(1.125rem, calc(1.125rem + (1.25 - 1.125) * ((100vw - 20rem) / (96 - 20))), 1.25rem);
}

.text-5xl {
  font-size: clamp(2.2806973457336426rem, calc(2.2806973457336426rem + (3.732479999999999 - 2.2806973457336426) * ((100vw - 20rem) / (96 - 20))), 3.732479999999999rem);
}
      
    

Any thoughts, suggestions. Let me know.

comments powered by Disqus

Maybe interesting…

UP