Responsive Layout

Simple et Efficace

The original article is from Temani Afif and can be read here .

We cannot talk about web development without talking about Reponsive Design. The latter is now a must and everyone will use Media Queries to build a responsive website.

Since the introduction of media queries (before 2000), CSS has evolved and now (in 2021) there are a lot of tricks that can help you drastically reduce the usage of media queries and create an optimized code. I will even show you how to replace multiple media queries with only one CSS declaration.

I will start with the trivial examples that are widely used but still limited:

flex & flex-wrap

Demo: https://codepen.io/t_afif/pen/zYNggoq

flex: 400px will set a base width equal to 400px. The items will then wrap if there isn’t enough space for the 400px. They will grow to fill the empty spaces and will shrink if the container width is bigger than 400px.

✔️ Easy to use, only 2 lines of code are required
❌ We cannot control when the items will wrap
❌ We cannot control the number of items per row
❌ The items of the last row will have a different width

auto-fit & minmax

Demo: https://codepen.io/t_afif/pen/wvgVVPN

Similar to the previous example, the repeat(auto-fit,minmax(400px,1fr)) will define the base width and we will have a similar wrapping behavior.

✔️ Easy to use, only 1 line of code is required
✔️ The items of the last row will keep the same width
❌ We cannot control when the items will wrap
❌ We cannot control the number of items per row
❌ We don’t have the shrink effect of the flexbox so we may face overflow

We will try to optimize the above examples with some CSS tricks in order to overcome the drawbacks.

Controling the number of items

In our first example, let’s change flex: 400px to flex: max(400px, 100%/3 - 20px). Resize the screen and you will notice that each row will not have more than 3 items (even for a large screen width).

Demo: https://codepen.io/t_afif/pen/abpeeeV

The logic is easy. When the screen width increase, 100%/3 will be bigger than 400px so it’s the max value that will get used. We cannot have more than 3 items per row if all them have a width equal to 100%/3.

What the hell is the 20px??

It’s twice the gap we defined. For 3 items we will have 2 gaps so for N items we should use max(400px, 100%/N - (N - 1)*gap).

We can still optimize the formula to remove the gap and use max(400px, 100%/(N + 1) + 0.1%). We tell the browser that each item will be equal to 100%/(N + 1) so N + 1 items per row but we add a tiny percentage (the 0.1%) thus one of the items will wrap and we end with only N items per row!

Demo: https://codepen.io/t_afif/pen/wvJwzbL

✔️ Now we can control the maximum number of items per row.

The same can also be applied to the CSS grid example as well:

Demo: https://codepen.io/t_afif/pen/BaWBLge

I have added CSS variables to easily control the different values.

Controling the shrink effect

Using CSS grid we may have overflow if the base width is bigger than the container width unlike with flexbox where we have the flex-shrink.

To overcome this we change max(400px, 100%/(N + 1) + 0.1%) to clamp(100%/(N + 1) + 0.1%, 400px, 100%).

Demo: https://codepen.io/t_afif/pen/ZEezBGL

✔️ We have our shrink effect and no more overflow

Controling the wrap

In all the previous examples, we have no control over the wrap. We don’t really know when it will happen. It depends on the base width, the gap, the container width, etc

To control this we will change our base width (the 400px) with (400px - 100vw)*1000 to get the following

clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%)

It looks a bit strange but easy to understand. The 100vw is our screen width and logically this value will change on screen resize while the 400px will remain fixed. This will lead us to the following logic:

Demo: https://codepen.io/t_afif/pen/BaWBQqK

We did our first media query!

We were able to move from N columns to 1 column without using @media and with only one CSS declaration. Our base width has become a breakpoint.

✔️ We can control when the items will wrap
✔️ We can control the number of items per row

What about moving from N columns to M columns?

We simply update our clamp() function like below:

clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%/(M + 1) + 0.1%)

I think everyone got the trick now. When the screen width is bigger than 400px we fall into the first rule (N items per row). When the screen width is samller than 400px we fall into the second one (M items per row).

Demo: https://codepen.io/t_afif/pen/ZEezBgo

We can easily control the number of items per row and we can decide when to change that number. All this using only one CSS declaration!

What about moving from N columns to M columns to 1 column ?

We can do this by nesting clamp() functions like below:

clamp(clamp(100%/(N + 1) + 0.1%, (W1 - 100vw)*1000,100%/(M + 1) + 0.1%), (W2 - 100vw)*1000, 100%)

We have two breakpoints so we will logically need two width (W1 and W2).

We can see our function like below:

clamp(clamp( .. ), (W2 - 100vw)*1000, 100%)

Let’s see this in play:

Demo: https://codepen.io/t_afif/pen/xxqKgZe

We did 2 media queries using only one CSS declaration! Not only this, but we can easily adjust that declaration using CSS variables which means that we can update the logic for different containers easily

Demo: https://codepen.io/t_afif/pen/mdWbRRE

How many media queries until now? well, I stopped the count …

You want more breakpoints? You simply nest another clamp() function and you have

From N columns to M columns to P columns to 1 column

Demo: https://codepen.io/t_afif/pen/bGqbgYY

We have our responsive design without any single media queries

✔️ Only one line of code
✔️ Easy to update using CSS variables
✔️ We can control the number of items per row
✔️ We can control when the items will wrap
✔️ We don’t have overflow on small screens
✔️ All the items have the same width
✔️ Each container can have its own breakpoints

Container Queries

Everyone is excited to use this new feature that consider the width of element instead of the screen to create media queries but no need to wait for it.

The trick I made already cover this feature. We simply change 100vw with 100% and all the logic we made previously will now consider the container width instead of the screen width.

Resize the below containers and see the magic in play

Demo: https://codepen.io/t_afif/pen/gOmYmgz

Bonus

I will end this post with a last trick that allow you to change the coloration of your items without using media queries as well.

div {
  background:
   linear-gradient(purple 0 0) 0 /calc(var(--w3) - 100vw) 1px,
   linear-gradient(blue   0 0) 0 /calc(var(--w2) - 100vw) 1px,
   linear-gradient(green  0 0) 0 /calc(var(--w1) - 100vw) 1px,
   red;
}

We consider 3 gradient layers plus a background-color. The size of each gradient is defined using one of the breakpoints. If calc() is negative then the gradient will not show. If calc() is positive then the size will also be positive and thanks to the repeat feature, it will cover all the area.

The order is very important. Below a table to better understand:

[0 W3][3 W2][W2 W1][W1 infinity]
✔️purple❌purple❌purple❌purple
✔️blue✔️blue❌blue❌blue
✔️green✔️green✔️green❌green
✔️red✔️red✔️red✔️red

The red color is always shown and at each breakpoint one of the gradient is displayed covering the bottom layer.

Here is a demo with all the features together. Run at full screen and resize:

Demo: https://codepen.io/t_afif/pen/wvJwdRW

To make the coloration work based on the container width, we update the code slightly and we use pseudo element that we position relatively to the container and we clip the overflow

Demo: https://codepen.io/t_afif/pen/zYZOwQJ

A related Stack Overflow question where I am using such trick: How to change the color of <div> Element depending on its height or width? . I am also changing the text coloration and the borders based on the width or the height.

That’s it!

Now you have a good trick that allow you to control your responsive layout without using media queries and with only few lines of code. Of course, this is not a replacement to media queries. It’s an optimization that can help you reduce the amount of code.