Fluid type scales with Less

03 May 2021
Web Development

Using Less CSS to manage fluid type scales.

If you haven’t already, have a look at Utopia: Fluid responsive design. It’s a fantastic resource about fluid typography for the web. Utopia should be your first port of call, as I don’t intend this article to say much (if anything) about the theory of fluid typography—rather, discuss how to implement it in Less.

Utopia provides a type scale calculator, which generates all the styles required to implement a type scale system. I wanted to create styles locally, so I turned to my favourite CSS preprocessor.

First, I needed to define some basic settings. I tried to keep as many as possible as native CSS variables. Some, however, need to be bound into Less for the generation to work.

Ideally, I would have liked a straightforward mixin to generate all possible type scales. However, I couldn’t figure out how to do this with recursive mixins. So I now have two: Positive and negative.

Here’s the code:

@width-min: 320;
@width-max: 1200;
@base: 16;

@type-min-scale: 1.2;
@type-min-size: 17;
@type-max-scale: 1.2;
@type-max-size: 21;
@typescale-up-steps: 10;
@typescale-down-steps: 2;

:root {
  --fluid-min-width: @width-min;
  --fluid-max-width: @width-max;

  --fluid-screen: 100vw;
  --fluid-bp: calc(
    (var(--fluid-screen) - var(--fluid-min-width) / @base * 1rem) /
      (var(--fluid-max-width) - var(--fluid-min-width))

#type {
    .positive-typescale(@counter: 0) when (@counter <= @typescale-up-steps) {
        @min: round(pow(@type-min-scale, @counter) * @type-min-size, 2);
        @max: round(pow(@type-max-scale, @counter) * @type-max-size, 2);
        :root {
            --step-@{counter}: calc((unit((@min/@base), rem)) + (@max - @min) * var(--fluid-bp));
        #type > .positive-typescale(@counter + 1); 
    .negative-typescale(@counter: 1) when (@counter <= @typescale-down-steps) {
        @min: round((@type-min-size / pow(@type-min-scale, @counter)), 2);
        @max: round((@type-max-size / pow(@type-max-scale, @counter)), 2);
        :root {
            --step--@{counter}: calc((unit((@min/@base), rem)) + (@max - @min) * var(--fluid-bp));
        #type > .negative-typescale(@counter + 1);  

The output from the above code similar to that of Utopia’s calculator. It’s probably worth discussing the @min and @max calculations, as this is hidden under the bonnet in Utopia. These can be written:

(scale ^ scale step) * base text size

For example, with a scale of 1.2 and a base text size of 16, the 3rd type size in this scale would be:

(1.2 ^ 3) * 16 = 27.65

Custom steps

In practice, I found I wanted to bridge more than one step on the scale. For example, a mega style that grows from step 6 on smaller screens to 12 at the maximum width. This is like Utopia’s custom space steps.

Here’s the code:

#type {
  /* translate between any steps within the system: */
  .steps(@minstep, @maxstep) {
	  @min: round(pow(@type-min-scale, @minstep) * @type-min-size, 2);
	  @max: round(pow(@type-max-scale, @maxstep) * @type-max-size, 2);
	  font-size: calc((unit((@min/@base), rem)) + (@max - @min) * var(--fluid-bp));