DEV Community

Cover image for Build your Responsive website without media query
Temani Afif
Temani Afif

Posted on • Updated on

Build your Responsive website without media query

We cannot talk about web development without talking about Responsive 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.

PS: you have to run all the examples outside DEV to better see the results since the embedded version is very small


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 in 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 in 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 to overcome the drawbacks.


Controlling the number of items

In our first example, let's change flex: 400px to flex: max(400px, (100% - 20px)/3). 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 of 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 - 1)*gap)/N).

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:

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

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


Controlling the shrink effect

Using CSS grid we may have an 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%).

  • For a large screen width, the 100%/(N + 1) + 0.1% will be bigger than 400px and we will get our maximum number of items.
  • For a small screen width, the 100% will be smaller than 400px and our items will not exceed the container width.

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

✔️ We have our shrink effect and no more overflow


Controlling the wrap

In all the previous examples, we have no control over the wrap. We don't 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%)
Enter fullscreen mode Exit fullscreen mode

It looks a bit strange but is 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:

  • When screen width (100vw) > 400px the difference will be negative so it will get clamped to the 100%/(N + 1) + 0.1% which is a positive value: We have N items per row

  • When screen width (100vw) < 400px the difference will be positive, we multiply with a big value (the 1000) so it will get clamped to the 100%: We have 1 item per row

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%)
Enter fullscreen mode Exit fullscreen mode

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 smaller 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 the below:

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

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

We can see our function like:

clamp(clamp( .. ), (W2 - 100vw)*1000, 100%)
Enter fullscreen mode Exit fullscreen mode
  • When the screen width is smaller than W2 we fall into 100%: one item per row
  • When the screen width is bigger than W2 we fall into the first clamp(): We do the logic there
    • when the screen width is smaller than W1 we fall into 100%/(M + 1) + 0.1%): M items per row
    • when the screen width is bigger than W1 we fall into 100%/(N + 1) + 0.1%): N items per row

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 ...

Do 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 an overflow on small screens
✔️ All the items have the same width
✔️ Each container can have its breakpoints


Container Queries

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

The trick I made already covers 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 the play

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


Bonus

I will end this post with a final trick that allows 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;
}
Enter fullscreen mode Exit fullscreen mode

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 is a table to better understand:

[0 W3[ [W3 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 gradients 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 use a 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 a 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 allows you to control your responsive layout without using media queries and with only a few lines of code. Of course, this is not a replacement for media queries. It's an optimization that can help you reduce the amount of code.


You want to support me?

buy me a coffee

OR

Become a patron

Top comments (19)

Collapse
 
artydev profile image
artydev

Great thank you :-)

Collapse
 
po0q profile image
pO0q 🦄 • Edited

that's clever, but I did not fully understand your point. Do you think we'd better stop using media queries?

With your techniques, the goal is to reduce the size of the CSS and kill some redundancy?

Collapse
 
afif profile image
Temani Afif • Edited

We cannot really stop using media queries but you can reduce them a lot especially when it comes to sizing. A lot of developers write many media queries to only update the number of columns or to switch from an horizontal to a vertical layout. Using my technique you can now avoid this.

The article is a kind of "think outside of the box" to show everyone that media query shouldn't be used automatically especially that now CSS has a lot of new features that can achieve complex things with few lines of code.

Collapse
 
ravavyr profile image
Ravavyr

Well done, my first reaction was "oh come on, you can't do sites without media queries; on real projects there are just too many pieces to do that without a lot of headaches".

As an experiment and proof of concept this is really neat though, nicely done. :)

Collapse
 
grahamthedev profile image
GrahamTheDev

Got to say I never quite got the P to N to M to 1 column query to work properly for me when I have tried to do it, so I look forward to playing with that one to see where I went wrong when I get to a PC!

Collapse
 
afif profile image
Temani Afif

it's probably an order issue. You need to pay attention to the orders and the values. In my example it's from N to M to P (N > M > P). Same for the breakpoints.

Collapse
 
grahamthedev profile image
GrahamTheDev

Quite possibly that! We will see if I can finally get my head around it for anything more complex than 3 breakpoints and I will bear that in mind!

Collapse
 
jaguart profile image
Jeff

Very nice explanations, thank you!

Did you do any performance comparisons vs @media? ...and esp. the approach of using @media to set just controlling css-variables.

I'm wondering why you would prefer css-calculations over @media?

Collapse
 
afif profile image
Temani Afif

No, I made no performance comparison but I don't think there is any issue with performance.

I'm wondering why you would prefer css-calculations over @media? --> simply because it allows me to have a very simple code. As you can see, I replaced 4 media queries with only one CSS declaration. This is a basic example. Imagine the case where you have a real project and how many line of code you can reduce using such trick.
In addition to that, we can easily adjust the breakpoints using CSS variables and have different breakpoints per element (you don't have such flexibility with media query).
Finally, You can see this as a different way of thinking. You will rarely find such article around the net so by showing you such trick I may lead you to think about simiar tricks using clamp()/max(), etc

Collapse
 
sgnilreutr profile image
sgnilreutr

Great article, this one goes straight into my bookmarks for safekeeping. Thanks!

Collapse
 
hassam7 profile image
Hassam Ali

you should create a udemy course. thanks for the article!

Collapse
 
wahyusa profile image
Wahyu Syamsul A'lam

Awesome

Collapse
 
micahlt profile image
Micah Lindley

Impressively well-written article! Some of these formulas are genius.

Collapse
 
afif profile image
Temani Afif

Thanks :) stay tuned for more article like this in the near future ;)

Collapse
 
ra1nbow1 profile image
Matvey Romanov

Pretty nice

Collapse
 
hemazyn profile image
Hemazyn

I will love to learn this, but I don't really understand.

Collapse
 
afif profile image
Temani Afif

what you don't understand?

Collapse
 
shepardm7 profile image
Sateek Roy

Amazing stuff @afif ! Super useful too! Thanks for sharing your knowledge. I'll be keeping this bookmarked for reference.

Collapse
 
isekaimaou1109 profile image
yoki kiriya

@afif excuse me, sir. "100%/(N + 1) + 0.1%" (this line i dont really understand why we N plus 1 and plus 0.1%)

Some comments have been hidden by the post's author - find out more