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)
Example 2 is simular in that they both:
have flex containers that have:
flex-flow: column wrap;
align-content: flex-start
use an invisible <div> after every odd flex item (starting on the 7th flex item).
invokes the invisible <div> if the last flex-item is every third flex item (starting on the 10th flex item).
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
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
*,
*::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>