8

I have a flexbox element that can have any number of children. These children can have a different width (but not more than 100%), and are all the same height, so they form tidy rows. Illustration:

[.....] [....]  
[.............]  
[..] [....] [.]  
[......]

I want to style the elements differently when there are more than three rows, than when there are only three, two or one row. Is there any way to do this with CSS?

I do not know how many child elements will be in one row. It could be one child per row, or two, or three. As in the illustration above. The elements are generated based on data that can change, so I can't fix them to x elements per row.

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

.parent .child {
  max-width: 100%;
  background: red;
}

// can I do this?
.parent:has-more-than-three-lines .child {
  background: blue;
}

I know I can use JavaScript to read the element's height and the children's height after it renders, and calculate how many rows there are. But I'd like to avoid that. If I have to give the child elements a fixed height, that's OK for me.

To be clear, I want to style all the children based on the parent's height, not just the ones in the additional rows.

KWeiss
  • 2,954
  • 3
  • 21
  • 37
  • @iLuvLogix there can be one, two or even three children in one row, so nth-child is probably not useful here. – KWeiss Apr 25 '19 at 11:18
  • Can you clarify: _"and they're all the same height"_ -> _"..I want to style the elements differently when there are more than three lines"_ – iLuvLogix Apr 25 '19 at 11:20
  • Since you do not want to use JS, the only solution would be to use `nth-child` or `:not:nth-child` IMO. – Jake Apr 25 '19 at 11:21
  • 1
    Rows and columns in flex / grid are not selectable and, therefore, cannot be targeted by CSS. https://stackoverflow.com/questions/46308048/how-to-target-a-specific-column-or-row-in-css-grid-layout – Michael Benjamin Apr 25 '19 at 11:22
  • @iLuvLogix the children are all the same height, the parent's height changes based on the number of children. "Lines" here refers to the rows of children, each child can only have one line of text in it. – KWeiss Apr 25 '19 at 11:25
  • You could use a wrapper as described in [this answer](https://stackoverflow.com/a/50734005/9793532).. – iLuvLogix Apr 25 '19 at 11:32
  • @iLuvLogix Browser support for `display: contents` is still [not that great](https://caniuse.com/#search=display%3A%20contents). – Jake Apr 25 '19 at 11:36
  • As @Michael_B said you cannot select them, either use JS to calculate the height of the container or apply a very complicated mix of nth-child, first-child, last-child and the + selectors – Jake Apr 25 '19 at 11:38
  • @Jake Thanks for the link! Yup - that doesn't look too promising.. – iLuvLogix Apr 25 '19 at 11:38
  • Isn't display: grid better solution? as well why don't add class for parent to tell what styling to use based on items in it. – Raimonds Apr 25 '19 at 13:36

2 Answers2

1

Here is an idea using multiple background in case you know the height of each element (i.e. the height of each row)

.parent {
  display: flex;
  flex-wrap: wrap;
  margin:10px;
  
  background:
    linear-gradient(blue,blue)     0 0/100% calc(100% - 4 * 50px),
    linear-gradient(yellow,yellow) 0 0/100% calc(100% - 3 * 50px),
    linear-gradient(grey,grey)     0 0/100% calc(100% - 2 * 50px), 
    linear-gradient(pink,pink)     0 0/100% calc(100% - 1 * 50px), 
    linear-gradient(black,black)   0 0/100% calc(100% - 0 * 50px);
}

.parent > div {
  max-width: 100%;
  height:50px;
  flex-grow:1;
  min-width:100px;
  border:1px solid red;
  box-sizing:border-box;
}

div.two {
  flex-basis:22%;
  border:1px solid blue;
}
div.three {
  flex-basis:44%;
  border:1px solid green;
}
<div class="parent">
<div class="two"></div>
<div ></div>
<div ></div>
<div class="two"></div>
<div ></div>
<div class="three"></div>
<div ></div>
<div class="two"></div>
<div class="three"></div>
<div class="three"></div>
</div>

<div class="parent">
<div class="two"></div>
<div ></div>
<div ></div>
<div class="two"></div>
<div ></div>
<div class="three"></div>
<div ></div>
<div class="two"></div>
<div class="three"></div>
<div class="three"></div>
<div ></div>
<div class="three"></div>
</div>


<div class="parent">
<div class="two"></div>
<div ></div>
<div ></div>
<div ></div>
<div class="two"></div>
<div class="three"></div>
<div class="three"></div>
</div>

<div class="parent">
<div class="two"></div>
<div ></div>
<div ></div>
<div ></div>
<div class="two"></div>
</div>

The trick is simple, we have different background layers and each layer is taking full width and a height equal to calc(100% - n * 50px). If the value of height is negative, the layer will not show but if it's positive it will show and since background-repeat is by default repeat, it will cover all the elements.

The order is also important. If for example we have 3 rows, the blue and the yellow will get a negative height and the other a positive one thus we will show the one on the top which the grey. We add another row, we make the yellow positive (it will show). We remove another row we make the grey negative and the pink will show.

Of course, this trick work only if you want to apply a background style to the element. We should find other trick in case you want to apply other styles.

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
-3

The only solution that I could find comes from an article from 2011 by Lea Verou avaible here.

As said in my comments, you'll have to use intricate nth-child selectors.

* {
  box-sizing: border-box;
  font-family: Arial, sans-serif;;
}

body {
  margin: 0;
}

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

.wrapper {
  align-items: flex-start;
}

.container {
  width: calc(1/3 * 100% - 2em);
  flex-direction: column;
  border: 1px solid #000;
  margin: 1em;

  /* Optional : if you want border-radius on the container */
  border-radius: 5px;
  overflow: hidden;
}

.item {
  padding: 1em;
}

/* Style for standalone element */

.item:nth-child(1):nth-last-child(1) {
  background: lemonchiffon;
}

/* Style for element that has 2 childs */

.item:nth-child(1):nth-last-child(2),
.item:nth-child(2):nth-last-child(1) {
  background: lightskyblue;
}

/* Style for element that has 3 childs */

.item:nth-child(1):nth-last-child(3),
.item:nth-child(2):nth-last-child(2),
.item:nth-child(3):nth-last-child(1) {
  background: thistle;
}

/* Style for element that has 4 childs */

.item:nth-child(1):nth-last-child(4),
.item:nth-child(2):nth-last-child(3),
.item:nth-child(3):nth-last-child(2),
.item:nth-child(4):nth-last-child(1) {
 background: gainsboro;
}

/* For further information, see http://lea.verou.me/2011/01/styling-children-based-on-their-number-with-css3/ */
<div class="wrapper">
  <div class="container">
    <div class="item">
      Item
    </div>
  </div>
  <div class="container">
    <div class="item">Item</div>
    <div class="item">Item</div>
  </div>
  <div class="container">
    <div class="item">Item</div>
    <div class="item">Item</div>
    <div class="item">Item</div>
    <div class="item">Item</div>
  </div>
</div>
Jake
  • 1,398
  • 1
  • 9
  • 19
  • Thank you for the link, but unfortunately, in my question, the number of child elements is not (necessarily) identical to the number of rows. So nth-child is not useful. – KWeiss Apr 25 '19 at 12:32
  • Than, as said previously, you'll need Javascript. – Jake Apr 25 '19 at 12:36