1

I am working on a responsive layout, in the larger screens it will have 3x3 layout. The 1st column will be filled first. Once there are 3 items in the first column, the 2nd column will start to fill. And when there are 6 items in the first 2 columns, the 3rd column will start to fill. So up to 9 items they will be filled in this logic. But if there are more than 9 items, they will create extra rows. Below picture is a good representation of what I am trying to create

enter image description here

I have tried both grid-auto-flow: row and grid-auto-flow: column but none working correctly. Please give me idea about how can this be implemented, if possible. If not what is the alternate way to do this?

1
  • Use JavaScript to Manually Place Items or you can also use CSS Grid with CSS Order. Commented Jun 11 at 10:06

2 Answers 2

2

A JavaScript Solution &
A CSS Solution


A. JavaScript (Example 1)

Assuming that you'll be using JavaScript (how else could the new elements be added?), this is the function that sets the max-height of the flex container (eg <main>):

Fig. 1 -- setLvl()

/**
 * Sets max-height of flex container. Every `<section>`
 * added on the (`idx` / 3) -2 click adds an invisible 
 * `<div class="block">` (in the example it has a low  
 * `opacity`) is added before  `<section>` with the 
 * `order` of (`qty` - 1 - `++mod`). On the next click
 * `div.block` is removed.
 * @param {number} qty - number of flex items
 * @return {number}    - number to multiply to the height
 *                       of a flex item + 3px
 */
const setLvl = (qty) => {
     let lvl, max;
     if (qty > 9) {
      lvl = Math.ceil(qty / 3);
      max = lvl * 3;
      if (max - 2 === qty) {
       ++mod;
       let block = `
         <div class="block" 
              style="order: ${qty - 1 - mod}">
         </div>`;
       secs()[qty - 4- mod]
        .insertAdjacentHTML("beforebegin", block);
      } else if (max - 1 === qty) {
       document.querySelector(".block").remove();
      }
     } else {
      lvl = 3;
     }
     return lvl;
    };

The flow of the flex items are possible with:

Fig. 2 (CSS and JS)

main {
  display: flex;
  flex-flow: column wrap;
  align-content: flex-start;
  ...
}


sec.style.order += idx;



B. CSS Solution (Example 2)

Using Temani Afif's Awesome Ruleset

Example 2 is simular in that they both:

  1. have flex containers that have:

    1. flex-flow: column wrap;
    2. align-content: flex-start
  2. use an invisible <div> after every odd flex item (starting on the 7th flex item).

  3. invokes the invisible <div> if the last flex-item is every third flex item (starting on the 10th flex item).

  4. increases the flex container max-height incrementally.

The missing key was this:

menu:has(> li:nth-of-type(10)) {
 --n: 4;
}

It says: "If a <menu> :has( a child > that's the (10)th <li> ), then set --n to 4."

The product of --n and 45px is the <menu> max-height. This selector alone does list items #3 and #4 (see previous list).

I went on and added two more rulesets along with strategically (unfortunately asemantically as well) placed hidden <div>s after every odd <li> starting on the 7th <li> (See #2 from the previous list).

The two rulests are as follows:

Fig. 3 -- 2 Rulesets that Resolve OP's Issue

menu:has(> li:nth-of-type(10)) li:nth-of-type(7) + div {
  display: block;
}

menu:has(> li:nth-of-type(11)) li:nth-of-type(7) + div {
  display: none;
}

The first one says: "If a <menu> :has( a child > that's the (10)th <li> ), then locate the <div> that follows the 7th <li> and reveal that hidden <div>"

The second one says the same thing except that it requires the 11th <li> to hide the <div>.


Examples 1 & 2

Example 1

JavaScript Solution

CodePen

let idx = 3;
let qty = 3;
let mod = 0;

const main = document.querySelector("main");
const io = document.forms.ui.elements;
const root = document.documentElement;

let secs = () => [...document.getElementsByTagName("section")];

const setLvl = (qty) => {
  let lvl, max;
  if (qty > 9) {
    lvl = Math.ceil(qty / 3);
    max = lvl * 3;
    if (max - 2 === qty) {
      ++mod;
      let block = `<div class="block" style="order: ${qty - 1 - mod}"></div>`;
      secs()[qty - 4 - mod].insertAdjacentHTML("beforebegin", block);
    } else if (max - 1 === qty) {
      document.querySelector(".block").remove();
    }
  } else {
    lvl = 3;
  }
  return lvl;
};

const onClick = (e) => {
  const sec = document.createElement("section");
  sec.dataset.idx = ++idx;
  sec.style.order = idx;
  main.append(sec);
  secs();
  qty = secs().length;
  root.style.setProperty("--max", setLvl(qty));
};

io.add[0].onclick = onClick;
io.add[1].onclick = onClick;
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

:root {
  --max: 3;
  font: 2ch/1.5 "Segoe UI";
}

body {
  width: 100vw;
  min-height: 100vh;
  padding: 2rem 1rem;
  overflow-y: scroll;
}

button {
  float: right;
  width: 6rem;
  aspect-ratio: 3/1.5;
  font: inherit;
  cursor: pointer;
}

main {
  display: flex;
  flex-flow: column wrap;
  align-content: flex-start;
  gap: 2px;
  width: 100%;
  max-height: calc((3rem + 3px) * var(--max));
  outline: 2px red dashed;
  outline-offset: 2px;
}

section {
  width: 33%;
  height: 3rem;
  font-size: 2rem;
  text-align: center;
  outline: 2px blue dashed;
  outline-offset: -2px;
}

section::before {
  content: attr(data-idx);
}

.block {
  height: 3rem;
  font-size: 2rem;
  text-align: center;
  outline: 2px black dashed;
  outline-offset: -2px;
  opacity: 0.3
}

.block::before {
  content: attr(class);
}
<form id="ui">
  <header>
    <button name="add" type="button">➕</button>
  </header>
  <main>
    <section data-idx="1"></section>
    <section data-idx="2"></section>
    <section data-idx="3"></section>
  </main>
  <footer>
    <button name="add" type="button">➕</button>
  </footer>
</form>


Example 2

CSS Only -- Using Temani Afif's Awesome Ruleset

CodePen

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

:root {
  --n: 3;
  font: 2ch/1.5 "Segoe UI";
}

body {
  width: 100vw;
  min-height: 100vh;
  overflow-x: hidden;
  overflow-y: scroll;
}

menu {
  display: flex;
  flex-flow: column wrap;
  align-content: flex-start;
  gap: 5px;
  max-height: calc(var(--n) * 45px);
  list-style: none;
  counter-reset: num;
  margin-bottom: 2.5px;
  padding: 0;
}

menu:has(> li:nth-of-type(10)) {
  --n: 4;
}

menu:has(> li:nth-of-type(10)) li:nth-of-type(7)+div {
  display: block;
}

menu:has(> li:nth-of-type(11)) li:nth-of-type(7)+div {
  display: none;
}

menu:has(> li:nth-child(13)) {
  --n: 5;
}

menu:has(> li:nth-of-type(13)) li:nth-of-type(9)+div {
  display: block;
}

menu:has(> li:nth-of-type(14)) li:nth-of-type(9)+div {
  display: none;
}

menu:has(> li:nth-child(16)) {
  --n: 6;
}

menu:has(> li:nth-of-type(16)) li:nth-of-type(11)+div {
  display: block;
}

menu:has(> li:nth-of-type(17)) li:nth-of-type(11)+div {
  display: none;
}

menu:has(> li:nth-of-type(19)) {
  --n: 7;
}

menu:has(> li:nth-of-type(19)) li:nth-of-type(13)+div {
  display: block;
}

menu:has(> li:nth-of-type(20)) li:nth-of-type(13)+div {
  display: none;
}

menu:has(> li:nth-of-type(22)) {
  --n: 8;
}

menu:has(> li:nth-of-type(22)) li:nth-of-type(15)+div {
  display: block;
}

menu:has(> li:nth-of-type(23)) li:nth-of-type(15)+div {
  display: none;
}

menu li {
  width: 33%;
  height: 40px;
  color: #fff;
  background: red;
  font: bold 20px sans-serif;
  counter-increment: num;
}

menu li:before {
  content: counter(num);
}

menu li+div.x {
  display: none;
  width: 33%;
  height: 40px;
}
<!-- 3 -->
<menu>
  <li></li>
  <li></li>
  <li></li>
</menu>
<!-- 10 -->
<menu>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <li></li>
</menu>
<!-- 13 -->
<menu>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</menu>
<!-- 16 -->
<menu>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</menu>
<!-- 19 -->
<menu>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</menu>
<!-- 22 -->
<menu>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <div class="x"></div>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</menu>

Sign up to request clarification or add additional context in comments.

5 Comments

Up 1! for the actually working snippet! But, would be nice to see a CSS-only solution, without JS - Tried quickly but failed, but somehow I think and believe it's possible.
Thanks Roko C. Buljan, I couldn't even get close with CSS because the pattern has a hiccup.
Exactly, with the initial 3-rows/col fill. Hard to overcome and suddenly fill horizontally only, once all the 3x3 are done, keeping always the items ordered as vertical,.. at least with the tricks I've tried.
Yep, step 9 to 10 is impossible, that's where that extra block was needed. I was thinking it might be possible to use a pseudo-element from either section 8 or 9 perhaps?
I think I got it, checkout Example 2.
1

You can control the number of columns based on the number of child elements. I have a tool from where you can generate the different selectors: https://css-tip.com/quantity-queries/

.container {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  grid-template-rows: repeat(var(--n,3),1fr);
  grid-auto-flow: column;
}

.container:has(> :nth-child(10)) {--n: 4}
.container:has(> :nth-child(13)) {--n: 5}
.container:has(> :nth-child(16)) {--n: 6}
.container:has(> :nth-child(19)) {--n: 7}
.container:has(> :nth-child(22)) {--n: 8}
/* and so on*/

/* extra CSS */
.container {
  counter-reset: num;
  border: 1px solid; 
  margin: 10px;
  gap: 5px;
}
.container div {
  height: 40px;
  background: red;
  color: #fff;
  font: bold 20px sans-serif; 
  counter-increment: num;
}
.container div:before {
 content: counter(num);
}
<div class="container">
  <div></div>
  <div></div>
  <div></div>
</div>
<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>
<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>
<div class="container">
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</div>

1 Comment

Thank you for helping, really appreciate :) Actually I tried this but the problem with this is: if say there are 10 items, then there will be 4 items in first column, 4 items in the second column and 2 items in third column. Instead we want 4 items in the first column, 3 items in the second column and 3 items in the third column. Any idea if that is possible at all?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.