If you like math and CSS, you’ll love this one. We don’t need to use media queries to change the values of some CSS properties —

*font-size*,

*padding*,

*margin*etc. — depending on the viewport width, with the CSS

`clamp`

function. But: we still need to use media queries for changing colors, borders, shadows and other CSS styles. This article is an enhanced release of the tutorial published earlier.Media queries are a great concept. Building a complex structure in an HTML document and adapting it for various devices is not often possible without them (for the moment at least). Here we will talk about the fact that one of the drawbacks of **fluid typography**, namely the appearance of a large number of media queries, can be avoided. Or, at least, the number of records in *@media* rule can be reduced.

The advent of `vw`

and `vh`

relative units, the `calc`

function, and later the `min`

, `max`

, `clamp`

functions in CSS gave us a lot of power. An exhaustive review of Modern Fluid Typography Using CSS Clamp has been recently published by Adrian Bece. I advise everyone to get acquainted with it.

The advantages of the ** clamp function** are obvious. We can define a change, for example, of a font size in a certain range of viewport (screen size) and, at the same time, limit it by maximum and minimum values. In such a simple case, we automatically (thanks to clamp) do not need to use media queries for changing sizes on breakpoints.

So, the following block in the CSS:

```
.block{ font-size: 2rem;
}
@media (max-width: 1200px) { .block{ font-size: calc(-1rem + 4vw); }
}
@media (max-width: 800px) { .block{ font-size: 1rem; }
}
```

…can be easily replaced using `clamp`

with a single line:

```
.block{ font-size: clamp(1rem, -1rem + 4vw, 2rem);
}
```

But what if we need to set a **more complex behavior** which is determined by variety of unique behavior on different ranges of the screen width? Look at the following modified code:

```
.block{ font-size: calc(-4rem + 8vw);
}
@media (max-width: 1200px) { .block{ font-size: calc(-1rem + 4vw); }
}
@media (max-width: 800px) { .block{ font-size: calc(0.5rem + 0.8vw); }
}
```

Can the `clamp`

help us again?

Let’s take a closer look: from simple to complex.

## Brief Digression Into Mathematics

As we all know, there is only one way to **draw a straight line** through two points. There are several ways to write equations to define the straight line. Below, it will be convenient for us to write the equation in the form:

$$(1)\;\;\; y=y_0 + k*x$$

where *y _{0}* is a

**point of intersection**of the line with the

`Y`

-axis (fig.1), `k`

parameter defines the slope of the straight line to the `X`

-axis and represents the growth/fall rate. Using the following canonical representation of the equation of a straight line:$$\frac{y – y_1}{y_1 – y_2}=\frac{x – x_1}{x_1 – x_2}$$

It is easy to connect the *y _{0}* and

`k`

parameters with the coordinates of the two points that belong to this line:$$(1a)\;\;\; k=\frac{y_2 – y_1}{x_2 – x_1} ,\;\;\; y_0=y_1 – k*x_1$$

It should be noted that in such problems it is convenient to **define the input parameters** (point coordinates) in pixels. But at the output the relative unit `rem`

is preferable. We should also remember the fact that viewport width is equal to `100vw`

(this follows from the definition of `vw`

unit). So, in equation (1) we must replace `x`

variable by just `100vw`

. Hence, we’ll have:

$$(1b)\;\;\; y=y_0 + k*100vw$$

Now it becomes clear the origin of expressions like `1rem + 2.5vw`

as one of the arguments of `clamp`

or `calc`

function. The first term (`1rem`

) is the *y _{0}* parameter expressed in relative units (

`rem`

), and the second one (`2.5vw`

) is the parameter `k`

multiplied by `100vw`

since `x=100vw`

. The choice of such units — relative unit (`rem`

) for values of output variable and viewport unit (`vw`

) for screen size — has been made due to **accessibility and responsiveness**, respectively.

So, now we know how to determine the parameters in the equation of the form (1) or (1b) of a straight line drawn through two points. This will be used in the next section.

## Main Formula Derivation For General Case

Now we’ll try to obtain an equation for `F(x)`

function that will follow the behavior of property in general case (fig.2). Some information below may be omitted for readers who do not like pure math. You can look at the **final equation (3)** and **the simple example** for explanation how to use it.

Let’s look at fig. 2a (below). As you can see from the figure, the behavior of the property is determined (tabulated) by `N`

points with coordinates *(x _{i}, y_{i})*. Let

*f*be a function that defines the straight line drawn through

_{i}(x)*(x*and

_{i}, y_{i})*(x*points. We have

_{i+1}, y_{i+1})`(N-1)`

such functions (fig2b). So, how to get a general `F(x)`

function that will be exactly equal to *f*function on the corresponding

_{i}(x)*[x*range, notably

_{i}, x_{i+1}]**completely repeat the behavior**of property from fig.2?

Let’s define the collection of functions *g _{i}(x)* as:

${{g}}_{{i}}{(}{x}{)}{=}{c}{l}{a}{m}{p}\left({{y}}_{{i}}{,}{{f}}_{{i}}{(}{x}{)}{,}{{y}}_{{i}{+}{1}}\right){.}$

According to `clamp`

function definition:

${{g}}_{{i}}{(}{x}{)}{=}\left\{\right)separators="|">\begin{array}{c}{{y}}_{{i}}{,}{}{x}{}{{x}}_{{i}}{;}\\ {{f}}_{{i}}{(}{x}{)}{,}{{x}}_{{i}}{\le}{x}{}{{x}}_{{i}{+}{1}{;}}{}\\ {{y}}_{{i}{+}{1}}{,}{x}{\ge}{{x}}_{{i}{+}{1}}{.}\end{array}$

Let’s sum the *g _{i}(x)* functions, and denote the result as

`G(x)`

function,${G}\left({x}\right){=}{{\sum}}_{{i}{=}{1}}^{{N}{\u2013}{1}}{{g}}_{{i}}\left({x}\right){.}{}$Now we can calculate the values of this function for different ranges of `x`

variable:

${G}{(}{x}{)}{=}\left\{\right)separators="|">\begin{array}{c}\begin{array}{c}\begin{array}{c}{{f}}_{{1}}{(}{x}{)}{+}{{y}}_{{2}}{+}{{y}}_{{3}}{+}{\dots}{+}{}{{y}}_{{N}{\u2013}{2}}{+}{{y}}_{{N}{\u2013}{1}}{,}{{x}}_{{1}}{\le}{x}{}{{x}}_{{2}}{;}\\ {{{y}}_{{2}}{+}{f}}_{{2}}{(}{x}{)}{+}{{y}}_{{3}}{+}{\dots}{+}{}{{y}}_{{N}{\u2013}{2}}{+}{{y}}_{{N}{\u2013}{1}}{,}{{x}}_{{2}}{\le}{x}{}{{x}}_{{3}}{;}\\ {{{y}}_{{2}}{+}{{y}}_{{3}}{+}{f}}_{{3}}{(}{x}{)}{+}{\dots}{+}{}{{y}}_{{N}{\u2013}{2}}{+}{{y}}_{{N}{\u2013}{1}}{,}{{x}}_{{3}}{\le}{x}{}{{x}}_{{4}}{;}\end{array}\\ {\vdots}\end{array}\\ {{y}}_{{2}}{+}{{y}}_{{3}}{+}{{y}}_{{4}}{+}{\dots}{+}{{y}}_{{N}{\u2013}{1}}{+}{{f}}_{{N}{\u2013}{1}}{(}{x}{)}{,}{{x}}_{{N}{\u2013}{1}}{\le}{x}{}{{x}}_{{N}}{;}\end{array}$

or

${G}{(}{x}{)}{=}{{f}}_{{i}}{(}{x}{)}{+}{C}{o}{n}{s}{t}{,}$

for

${{x}}_{{i}}{\le}{x}{<}{{x}}_{{i}{+}{1}}{,}$${i}{=}{\mathrm{1,}}{\dots}{,}\left({N}{\u2013}{1}\right){,}$${C}{o}{n}{s}{t}{=}{\sum}_{{j}{=}{2}}^{{N}{\u2013}{1}}{{y}}_{{j}}$

It follows that `G(x)`

function is equal to corresponding *f _{i}(x)* function for each [x

_{i}, x

_{i+1}] ranges, after deduction of

`Const`

constant term, or…${F}\left({x}\right){=}{G}\left({x}\right){\u2013}{C}{o}{n}{s}{t}{=}{\sum}_{{i}{=}{1}}^{{N}{\u2013}{1}}{c}{l}{a}{m}{p}\left({{y}}_{{i}}{,}{{f}}_{{i}}{(}{x}{)}{,}{{y}}_{{i}{+}{1}}\right){\u2013}{\sum}_{{i}{=}{2}}^{{N}{\u2013}{1}}{{y}}_{{i}}$

Equation (3) represents the final result and is the solution to the problem.

**Several remarks** should be made here:

- If we have the range with
*y*then_{i}= y_{i+1}*f*, and_{i}(x)= y_{i}*clamp(y*properly. This fact will give us some simplification in the final expression for_{i}, y_{i}, y_{i}) = y_{i}`F(x)`

function. - If we have the range where the
*y*inequality is satisfied, then we should write the corresponding_{i}>y_{i+1}*g*function in the following form:_{i}(x)

… because of `clamp`

function definition.

- Let’s consider the ranges where
*х<х*and_{1}*х>х*. From equation (3) it follows that the values of property inside these ranges will be constant and equal to_{N}*y*and_{1}*y*, correspondingly. We can do nothing and leave it like that. But in these intervals, I prefer that the_{N}**values continue to change**according to the same laws as in the*[x*and_{1}, x_{2}]*[x*ranges. Therefore,_{N-1}, x_{N}]*g*and_{1}(x)*g*functions should be written not by_{N-1}(x)`clamp`

function but through`min`

or`max`

functions depending on the increasing or decreasing of the values in the given ranges. If we take the behavior of the property from fig.2 as an example, then we will have the following two redefinitions:

- It is possible to set an
**abrupt change**in the property. In this case, the maximum difference between*х*and_{i}*х*is set to_{i+1}`1px`

(fig.3) or, which is even more convenient in practice, even less than 1px. - Obviously, the more complex (more ranges) the behavior of the property is, the longer the resulting function will be and vice versa.
- Because of the possible complex structure of the function by equation (3), it must be used as an argument of the CSS
`calc`

function in the CSS stylesheet.

### Simple Example

Let’s simulate the following simple case. We have a header with a logo and menu items on some page. For mobile devices we need to create a **hamburger menu**. In this case, the font size of menu items, for example, is equal to `18px`

at `1920px`

of screen size and decreases to `12px`

at the viewport width of `768px`

. In the range from `320px`

to `767.98px`

of viewport width, the font-size is fixed at `20px`

(fig 4a.). This behavior of font size can be described by equation (3). Let’s start.

1. We need to calculate the parameters for *f _{1}*,

*f*and

_{2}*f*lines according to equation (1a) for its representation in the (1b) form. For

_{3}*f*function we have (using the coordination of 1 and 2 points):

_{1}So, using equation (1b):

The same procedure for another two lines gives us:

and

and

2. Now we can construct the *g _{i}* functions using equation (2):

3. The last term (constant) in equation (3) can be determined:

4. Desired form of equation (3) is…

or, in the final form:

5. Final record in CSS file will be (by remark 6):

`.block{ font-size: calc(clamp(0.75rem, 19200.75rem – 40000vw, 1.25rem) + max(0.75rem, 0.5rem + 0.5208333vw) – 0.75rem); }`

… and is fully equivalent to:

```
.block{ font-size: calc(0.5rem + 0.5208333333vw);
}
@media (max-width: 767px) { .block { font-size: 1.25rem; }
}
```

Evaluations for modeling right margin behavior (see fig. 4b) give the following equation (everyone can verify this):

You can check this example here:

### Another Example

The following dependence of `padding`

:

```
.block { padding: 3rem;
} @media (max-width: 1199px) { .block { padding: 2.5rem; }
}
@media (max-width: 991px) { .block { padding: 2rem; }
}
@media (max-width: 767px) { .block { padding: 1.5rem; }
}
@media (max-width: 575px) { .block { padding: 1rem; }
}
```

… can be also easily replaced by:

```
.block { padding: calc(clamp(1rem, -14398.5rem + 40000vw, 1.5rem) + clamp(1.5rem, -19198rem + 40000vw, 2rem) + clamp(2rem, -24797.5rem + 40000vw, 2.5rem) + clamp(2.5rem, -29997rem + 40000vw, 3rem) - 6rem);
}
```

So, equation (3) gives us the possibility to **describe any arbitrary behavior** of the property depending on the viewport width. And it should be pointed out, we achieved this by completely eliminating the use of media queries.

Obviously, formula (3) can be used to describe **not only the font size**, but also every other property whose dimension is a unit of length. This limitation is due to a limitation in CSS itself, namely the lack of support for mathematical operations on values that have dimensions. For example, we cannot calculate something like

`(2px*6px)/4px`

in CSS at the date. I hope that such restrictions will disappear in future, and the equation (3) will be applied to properties specified as a percentage or dimensionless.Also, it should be noted that it makes sense to apply the equation (3) only when the dependence is more complex than in **primitive cases** shown below:

**Continuous unlimited change**, the behavior is determined by two points only,`k`

parameter of`f`

function can be positive, negative or even zero (see fig.5a,`k>0`

, for example). Instead of the equation(3) we use a function of the form`calc(f(x))`

. A font size of some text that monotonically changes with viewport width can be as an example.**Behavior is determined by three points**, for`k`

parameters of*f*and_{1}*f*functions the inequality_{2}*k*is satisfied (see fig.5b,_{1}>k_{2}*k*and_{1}>0*k*, for example). In such case,_{2}<0*min(f*function should be used instead of equation (3). A font size of some text that monotonically changes with viewport width to a certain value and then keeps a constant value can be used as an example._{1},f_{2})- The same as the second case, but the inequality
*k*is satisfied (see fig.5c,_{1}<k_{2}*k*and_{1}<0*k*, for example). In such case,_{2}>0*max(f*function should be used instead of equation (3)._{1},f_{2})

Everyone can write their own mixin or function based on the equation (3) or use my implementation as CSS functions that take into account the above **remarks** and **primitive cases**.

You can also find a **simple example** with the flex structure here. In this example, the width of flex elements is calculated using the equation (3). This is an attempt to avoid media queries with such a **commonly used transformation** — initially the width of each of the four flex items is set to 25% (convert to px units, of course), and then, as the viewport width decreases, it changes to 50%, and then to 100%. It even seems possible to take into account the value of gap, and make it also responsive.

But the example will break as soon as there is a **vertical scroll**. To avoid example breaking and to compensate for scrollbar, we can replace `vw`

units by `%`

in expressions for width of flex elements. But while CSS restrictions do not allow us to use the equation (3) directly for values given in percentages, it is better to abandon such idea.

## Summary

I’d like to mention that the idea was just to proving the **possibility of such an approach**, and the suitability of its application is your own choice. Of the obvious disadvantage of this approach, I have to highlight that the resulting code becomes less readable. The advantage is that media queries remain solely for describing the logic of changes while adaptive, structural changes and are not clogged with technical lines of changing font sizes, paddings, margins etc.

*Editor’s note*: It would be fantastic to build up a little tool that would allow developers to define changes and construct these complex queries in no time. Still, the maintainance would become quite a hassle — unless we use a Sass-mixing for it, maybe? We kindly thank Ruslan for taking the time to work on this, and oh my, what a journey it was!

### Acknowledgements

*Thanks to Evgeniy Andrikanich and his YouTube channel “FreelancerStyle” for creation of free high-quality educational content (HTML, CSS, JS).*

### Sources

(vf, yk, il)