4

Looked into a few questions here but they don't quite solve what I'm looking for.

Say I have a website and I want. On desktop I want this:

enter image description here

This is easy. grid-template-columns: repeat(3, 33%) (basically)

On mobile, however, I want this

enter image description here

What I'm running into is happens before it flips to a single column:

enter image description here

I'm trying clamp(), minmax(), and all sorts of things but nothing ever works as I want. Yes, I can totally use a media query but I was hoping to create a truly fluid grid/flex layout using modern CSS like clamp, grid, minmax, etc so there wouldn't be a need for media queries for basic layout changes.

I know this doesn't work but as a starting point as requested here's a simple version of one of my 100 attempts :) In this version I was trying to use clamp to switch from a repeat(3) to repeat(1).

.wrapper {
  display: grid;
  gap: 15px;
  grid-template-columns: repeat(clamp(1, calc(100% - 500px), 3), 33%);
}

.one {
  background: red;
}

.two {
  background: green;
}

.three {
  background: blue;
}
<div class="wrapper">
  <div class="item one"><h3>Example A</h3></div>
  <div class="item two"><h3>Example Two</h3></div>
  <div class="item three"><h3>Third Example</h3></div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Oscar Godson
  • 31,662
  • 41
  • 121
  • 201

3 Answers3

6

Full article with more generic rules: https://css-tricks.com/responsive-layouts-fewer-media-queries/

Here is an idea using max(0px, (400px - 100vw)*1000) inside flex-basis. This formula will eiter give 0px if 100vw (screen size) is bigger than 400px or a very big value in the opposite case giving each element a big flex-basis and create a wrapping. Simply adjust the 400px which play the role of @media (max-width:400px)

.container {
  display:flex;
  flex-wrap:wrap;
}

.container div {
  height:100px;
  border:2px solid;
  background:red;
  flex-basis:max(0px, (400px - 100vw)*1000);
  flex-grow:1;
}
<div class="container">
  <div></div>
  <div></div>
  <div></div>
</div>

Using CSS grid it can be like below:

.container {
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(clamp(30%, (400px - 100vw)*1000, 100%),1fr));
  grid-gap:5px;
}

.container div {
  height:100px;
  border:2px solid;
  background:red;
}
<div class="container">
  <div></div>
  <div></div>
  <div></div>
</div>

A similar question where I am controling the maximum number of columns without media query: CSS grid - maximum number of columns without media queries


We can scale the above solution to consider more complex cases.

Example of moving from 6 to 3 to 1 column:

.container {
  display:grid;
  grid-template-columns:
    repeat(auto-fill,
      minmax(clamp(clamp(15%,(800px - 100vw)*1000, 30%), (400px - 100vw)*1000, 100%)
      /* if(screen> 800px) 15% elseif(screen> 400px) 30% else 100% */
      ,1fr));
  grid-gap:5px;
}

.container div {
  height:100px;
  border:2px solid;
  background:red;
}
<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

To understand the values consider the following ranges:

100%/7  100%/6  100%/5  100%/4  100%/3  100%/2  100%/1
 14.3%  16.7%    20%     25%     33.3%   50%     100%

To get 6 columns we need a value in the range ]14.3% 16.7%] (I considered 15%) To get 3 columns we need a value in the range ]25% 33.3%] (I considered 30%)

We simply avoid the edges to make sure we account for the gaps.

A more generic solution using CSS variables where I will add 0.1% to make sure the value is big enough to get the needed number of column and it can hold the gap.

Let's also add some dynamic coloration (related: How to change the color of <div> Element depending on its height or width?)

.container {
  /* first breakpoint*/
  --w1:800px;
  --n1:6;
  /* second breakpoint*/
  --w2:400px;
  --n2:3;

  display:grid;
  grid-template-columns:
    repeat(auto-fill,
      minmax(clamp(clamp(100%/(var(--n1) + 1) + 0.1%, (var(--w1) - 100vw)*1000,
                         100%/(var(--n2) + 1) + 0.1%), (var(--w2) - 100vw)*1000,
                         100%), 1fr));
  grid-gap:5px;
  margin:10px 0;
}

.container div {
  height:100px;
  border:2px solid;
  background:
    linear-gradient(blue  0 0) 0 /1% calc(var(--w2) - 100vw),
    linear-gradient(green 0 0) 0 /1% calc(var(--w1) - 100vw),
    red;
}
<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

<div class="container" style="--w1:900px;--n1:8;--w2:500px;--n2:4;grid-gap:10px;">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

<div class="container" style="--w1:600px;--n1:4;--n2:2;grid-gap:2vw;">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

Using flexbox where we can have a different (probably wanted) behavior where the last item of a row will take all the free space:

.container {
  /* first breakpoint*/
  --w1:800px;
  --n1:6;
  /* second breakpoint*/
  --w2:400px;
  --n2:3;

  display:flex;
  flex-wrap:wrap;
  margin:10px 0;
}

.container div {
  height:100px;
  border:2px solid;
  margin:5px;
  flex-basis:clamp(clamp(100%/(var(--n1) + 1) + 0.1% ,(var(--w1) - 100vw)*1000, 
                         100%/(var(--n2) + 1) + 0.1%),(var(--w2) - 100vw)*1000, 
                         100%);
  flex-grow:1;
  box-sizing:border-box;
  background:
    linear-gradient(blue  0 0) 0 /1% calc(var(--w2) - 100vw),
    linear-gradient(green 0 0) 0 /1% calc(var(--w1) - 100vw),
    red;
}
<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

<div class="container" style="--w1:900px;--n1:8;--w2:500px;--n2:4;">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

<div class="container" style="--w1:600px;--n1:4;--n2:2;">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
0

You can achive this by using grid like this:

.btnContainer {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); /* here you set when the width should change to be moved to the next row, in this example, the divs will move when the screen reduces size and their width is less than 200px */
}

.btnCenter3 {
  background-color: rgb(6, 198, 247);
  height: 400px;
}
.btnCenter4 {
  height: 400px;
  background-color: rgb(196, 95, 1);
}
.btnCenter5 {
  height: 400px;
  background-color: rgb(192, 231, 19);
}
<div class="btnContainer">
<div class="btnCenter3"></div>
      <div class="btnCenter4"></div>
      <div class="btnCenter5"></div>
 </div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Alex Yepes
  • 1,160
  • 1
  • 9
  • 21
  • Don't understand the downvote. If you check the code snippet, it does what you asked. In desktop mode it will show three columns, and on mobile screens it will show 1 column. No media queries were used! – Alex Yepes Jan 18 '21 at 21:28
  • 2
    I didn't downvote but this runs into the same issue i described. It goes blue brown row 1 and then has green in row 2. – Oscar Godson Jan 18 '21 at 21:30
0

@Temani's answer is bonkers but great :). I needed to implement a similar thing for a 4 column layout (breaking to 2 cols then 1 col) but found that replacing the 15 and 30 percentage values for 25 and 50 didn't work. This seems to be related to the fact that the percentages need to take account of the grid gap, so @Temani's answer only really works because of the rounding 'error'. So a more robust (if even more bonkers) solution, based on a 4 col grid is:

:root {
  --grid-gap: 10px;
  --grid-gap-x2: calc(var(--grid-gap));
}

.container {
  display:grid;
  grid-gap: var(--grid-gap-x2);
  grid-template-columns:
    repeat(auto-fill, minmax(clamp(clamp(calc(25% - var(--grid-gap-x2)),(800px - 100vw)*1000, calc(50% - var(--grid-gap-x2))), (400px - 100vw)*1000, 100%)
      /* if(screen> 800px) 25% elseif(screen> 400px) 50% else 100%. */
      /* (Subtracting grid gap from either side of percentage width.) */
      ,1fr));
}
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Dan
  • 5,836
  • 22
  • 86
  • 140
  • *answer only really works because of the rounding 'error* --> it's not errors, I rely one the 1fr feature so I don't need to account for that gap, I simply need to make it big enough to get the needed number of columns. 30% is bigger than 25% and smaller than 33% so with the 1fr I will get the 3 columns. same logic for the 15% – Temani Afif Jan 30 '21 at 19:27