Home > Software design >  Make elements fill rows but overflow to new rows after three columns, filling width / spaced evenly
Make elements fill rows but overflow to new rows after three columns, filling width / spaced evenly

Time:11-16

I'm trying to make elements fill a container row-by-row (not masonry), expanding to completely fill the row and spaced evenly, with up to three elements per row. (I think this must be covered by previous questions, but I haven't been able to find what I'm looking for.)

This snippet shows the result I'm looking for with one element, two elements, three, etc. (using grid in this case, but I'm not bothered whether it's grid, flex, or whatever), but it's seriously ugly and requires I have CSS classes for each variant (columns-1, columns-2, etc.) and that I specify the right class for its contents on the container:

/* Basic Styles */
html,
body {
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
    font-family: sans-serif;
    box-sizing: border-box;
}

*, *:before, *:after {
  box-sizing: inherit;
}

body {
    padding: 8px;
}

h2 {
    margin: 0;
    margin-top: 0.5rem;
    font-size: inherit;
}

/* The container for items */
.container {
    width: 500px;
    border: 1px solid black;
    padding: 8px;

    display: grid;
    grid-row-gap: 4px;
    grid-column-gap: 4px;
    justify-items: stretch;
}

/* An item in the container */
.container > span {
    background-color: #8ab1de;
    color: #1a2550;
    font-weight: 600;
    min-width: fit-content;
    padding: 4px;
    white-space: nowrap;
    flex-basis: auto;
    text-align: center;
}

/* Additional styles for container for ONE item */
.container.columns-1 {
    grid-template-columns: 1fr;
}
/* Additional styles for container for TWO items */
.container.columns-2 {
    grid-template-columns: 1fr 1fr;
}
/* Additional styles for container for THREE items */
.container.columns-3 {
    grid-template-columns: 1fr 1fr 1fr;
}
/* Additional styles for container for FOUR items */
.container.columns-4 {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
}
/* Additional styles for container for FIVE items */
.container.columns-5 {
    grid-template-areas:
        "a a b b c c"
        "d d d e e e";
}
.container.columns-5 :nth-child(1) {
    grid-area: a;
}
.container.columns-5 :nth-child(2) {
    grid-area: b;
}
.container.columns-5 :nth-child(3) {
    grid-area: c;
}
.container.columns-5 :nth-child(4) {
    grid-area: d;
}
.container.columns-5 :nth-child(5) {
    grid-area: e;
}

/* Additional styles for container for SIX items */
.container.columns-6 {
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: 1fr 1fr;
}

/* Seven or more would be unhandled */
<h2>One item:</h2>
<div >
    <span>Some Item</span>
</div>

<h2>Two:</h2>
<div >
    <span>Some Item</span>
    <span>Some Other Item</span>
</div>

<h2>Three:</h2>
<div >
    <span>Some Item</span>
    <span>Some Other Item</span>
    <span>Another Item</span>
</div>

<h2>Four:</h2>
<div >
    <span>Some Item</span>
    <span>Some Other Item</span>
    <span>Another Item</span>
    <span>A Fourth Item</span>
</div>

<h2>Five:</h2>
<div >
    <span>Some Item</span>
    <span>Some Other Item</span>
    <span>Another Item</span>
    <span>A Fourth Item</span>
    <span>A Fifth Item</span>
</div>

<h2>Six:</h2>
<div >
    <span>Some Item</span>
    <span>Some Other Item</span>
    <span>Another Item</span>
    <span>A Fourth Item</span>
    <span>A Fifth Item</span>
    <span>A Sixth Item</span>
</div>

<h2>And so on...</h2>

So:

  • For one, two, or three elements, put them in a single row filling the available space fairly evenly
  • For four elements, put two on each row, with four elements of equal size
  • For five elements, put three spaced evenly in the first row and two in the second
  • For six elements, three per row of equal size

Ideally, with unvarying markup other than the actual items, so for one element:

<div >
    <span>Some Item</span>
</div>

for three:

<div >
    <span>Some Item</span>
    <span>Some Other Item</span>
    <span>Another Item</span>
</div>

and so on.

In an ideal world, the number of elements per row would be dynamically determined from their content (akin to fit-content) rather than being the arbitrary number I've given (3), but with the specific use case I have, if I have to pick an arbitrary number, I can.

I'm trying to solve this with CSS, rather than JavaScript. If it's not possible, then I'll write the JavaScript to do it, but I'm hoping my struggle here is my CSS ignorance.

CodePudding user response:

Here is an idea using flexbox where you adjust the flex-basis based on the number of elements using nth-child

h2 {
  margin: 0;
  margin-top: 0.5rem;
  font-size: inherit;
}

.container>span {
  background-color: #8ab1de;
  color: #1a2550;
  font-weight: 600;
  padding: 4px;
  white-space: nowrap;
  text-align: center;
}

.container {
  width: 500px;
  border: 1px solid black;
  padding: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px; /* only column */
}

.container>* {
  flex: 1; /* same width at each row */
}

/* when 4 elements */
.container > :first-child:nth-last-child(4),
.container > :first-child:nth-last-child(4) ~ * {
  flex-basis: 40%; /* bigger than 33% and smaller than 50% */
}
/* when 5 elements and more */
.container > :first-child:nth-last-child(n   5),
.container > :first-child:nth-last-child(n   5) ~ * {
  flex-basis: 30%; /* bigger than 25% and smaller than 33% */
}
<h2>One item:</h2>
<div >
  <span>Some Item</span>
</div>

<h2>Two:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
</div>

<h2>Three:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
</div>

<h2>Four:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
</div>

<h2>Five:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
</div>

<h2>Six:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
  <span>A Sixth Item</span>
</div>

<h2>And so on...</h2>

Going to 9 elements:

h2 {
  margin: 0;
  margin-top: 0.5rem;
  font-size: inherit;
}

.container>span {
  background-color: #8ab1de;
  color: #1a2550;
  font-weight: 600;
  padding: 4px;
  white-space: nowrap;
  text-align: center;
}

.container {
  width: 500px;
  border: 1px solid black;
  padding: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px; /* only column */
}

.container>* {
  flex: 1; /* same width at each row */
}

/* when 4 elements */
.container > :first-child:nth-last-child(4),
.container > :first-child:nth-last-child(4) ~ *{
  flex-basis: 40%;
}
/* when 5 elements and more */
.container > :first-child:nth-last-child(n   5),
.container > :first-child:nth-last-child(n   5) ~ * {
  flex-basis: 30%;
}
/* when 7 elements */
.container > :nth-child(4):nth-last-child(4),
.container > :nth-child(4):nth-last-child(4) ~ *{
  flex-basis: 40%;
}
<h2>One item:</h2>
<div >
  <span>Some Item</span>
</div>

<h2>Two:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
</div>

<h2>Three:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
</div>

<h2>Four:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
</div>

<h2>Five:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
</div>

<h2>Six:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
  <span>A Sixth Item</span>
</div>

<h2>Seven:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
  <span>A Sixth Item</span>
  <span>A 7 Item</span>
</div>

<h2>Eight:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
  <span>A Sixth Item</span>
  <span>A 7 Item</span>
  <span>A 8 Item</span>
</div>

<h2>Nine:</h2>
<div >
  <span>Some Item</span>
  <span>Some Other Item</span>
  <span>Another Item</span>
  <span>A Fourth Item</span>
  <span>A Fifth Item</span>
  <span>A Sixth Item</span>
  <span>A 7 Item</span>
  <span>A 8 Item</span>
  <span>A 9 Item</span>
</div>

  •  Tags:  
  • css
  • Related