By Russell Winborn


2017-05-23 08:04:00 8 Comments

Wanted: a CSS only solution to enable MULTIPLE equal height grid "sections" on a per row basis, that is also responsive.

Note: this is a follow-up question to this question which has only a single "equal height" section per item - which can be achieved through flexbox

The below diagram should help explain the requirement: Helpful Diagram

The "item grid" should be responsive - in that it can show a different number of cards per row based on viewport width (4 on desktop, 2 on mobile). Within a given row, the equivalent "content" and "feature" sections should have the same height.

In the below HTML & CSS - the item cards are split into the rows that we need (at the two example break points desktop & mobile) but the content section heights are variable:

.items {
  max-width: 1200px;
}

.item {
  width: 25%;
  box-sizing: border-box;
  display: inline-block;
  vertical-align: top;
  padding: 0 12px;
  margin: 24px -4px 24px 0;
}

@media (max-width: 600px) {
  .item {
    width: 50%;
  }
}

.item__heading {
  background-color: #d4d0f5;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
}

.item__content {
  padding: 10px;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
}

.item__features {
  padding: 10px;
  border-top: 1px solid #bbbbbb;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
  background-color: #f7cbb1;
}

.item__features ul {
  margin: 0px;
}

.item__price {
  background-color: #e0f6d9;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
}
<div class="items">

  <div class="item">
    <div class="item__heading">
      Item 1
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £99.99
    </div>
  </div>


  <div class="item">
    <div class="item__heading">
      Item 2
    </div>
    <div class="item__content">
      Some content that is longer than other items on the same row and sets the height of this section as it spans many more lines than the rest of the other content sections on this row
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £69.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 3
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>                         <li>feature 2</li>
        <li>feature 3</li>
      </ul>
    </div>
    <div class="item__price">
      £69.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 4
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £109.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 5
    </div>
    <div class="item__content">
      Some content that is a medium kind of length blah blah
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £29.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 6
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
        <li>feature 2</li>
      </ul>
    </div>
    <div class="item__price">
      £99.99
    </div>
  </div>

    
</div>

I created the following codepen as a JavaScript based solution that achieves the desired outcome - but I am hoping to replace this with just a CSS solution if possible: http://codepen.io/rusta/pen/xdmdxm

Limitations

  • The number of items to be displayed in the grid list can be any number from 1-n
  • The size of the "content" and "feature" sections to be displayed will be genuinely variable (so picking a "sensible" min-height is not an option)

Flexbox based solutions seem to be un-able to cope with the fact that the items have more than one section that needs to be aligned

I was hoping that the new CSS Grid system would help achieve the above, but I have made several attempts at this with no luck, so am opening it up the community to see if I am just missing something

Further note: I say a CSS only solution, by which I mean a non-JS solution. If the HTML blocks need to change (order/nesting/class names) to support a non-JS solution that's a viable option

2 comments

@Ason 2017-05-24 17:00:21

Based on your own answer, where you grouped them by 4, you can do that with CSS Flexbox too.

To make them behave when there is less than 4, it might be possible to accomplish that using the nth-child selector, but it was simpler to use a last* class, so I went for the latter.

One might even be able to do this without the .group_of_4 wrapper, with some clever nth-child rules, but again, went for the simpler since it does not come with any obvious limitations

Fiddle demo

.items {
  display: flex;
  flex-direction: column;
  max-width: 1200px;
}

.items .group_of_4 {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;        /*  updated  */
}

.items .group_of_4 ~ .group_of_4 {
  margin-top: 24px;
}

.items .group_of_4 > div {
  width: calc(25% - 12px);                /*  updated  */
  box-sizing: border-box;
  padding: 12px;
}


.item__heading {
  background-color: #d4d0f5;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
  order: 1;
}

.item__content {
  padding: 10px;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
  order: 2;
}

.item__features {
  padding: 10px;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
  background-color: #f7cbb1;
  order: 3;
}

.item__price {
  background-color: #e0f6d9;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
  order: 4;
}

/* one item in a group */
.items .group_of_4 .last1 {
    margin-right: calc(75%  6px);        /*  updated  */
}
/* two items in a group */
.items .group_of_4 .last2 {
    margin-right: calc(50% + 6px);       /*  updated  */
}
/* three items in a group */
.items .group_of_4 .last3 {
    margin-right: calc(25% + 6px);       /*  updated  */
}

@media (max-width: 600px) {
  .items .group_of_4 > div:nth-child(8) ~ .item__heading {
    margin-top: 24px;
    order: 5;
  }
  .items .group_of_4 > div:nth-child(8) ~ .item__content {
    order: 6;
  }
  .items .group_of_4 > div:nth-child(8) ~ .item__features {
    order: 7;
  }
  .items .group_of_4 > div:nth-child(8) ~ .item__price {
    order: 8;
  }
  
  .items .group_of_4 > div {
    width: calc(50% - 12px);             /*  updated  */
  }

  /* one item in a group */
  /* three items in a group */
  .items .group_of_4 .last1,
  .items .group_of_4 .last3 {
    margin-right: 50%;
  }
  /* two items in a group */
  .items .group_of_4 .last2 {
    margin-right: 0%;
  }  
}
<div class="items">

  <div class="group_of_4">
    <div class="item__heading">
      Item 1
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £99.99
    </div>

    <div class="item__heading">
      Item 2
    </div>
    <div class="item__content">
      Some content that is longer than other items on the same row and sets the height of this section as it spans many more lines than the rest of the other content sections on this row
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £69.99
    </div>

    <div class="item__heading">
      Item 3
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
        <li>feature 2</li>
        <li>feature 3</li>
      </ul>
    </div>
    <div class="item__price">
      £69.99
    </div>

    <div class="item__heading">
      Item 4
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £109.99
    </div>
  </div>
  
  
  <div class="group_of_4">
    <div class="item__heading">
      Item 5
    </div>
    <div class="item__content">
      Some content that is a medium kind of length blah blah
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £29.99
    </div>

    <div class="item__heading last2">
      Item 6
    </div>
    <div class="item__content last2">
      Some content that is not that long
    </div>
    <div class="item__features last2">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price last2">
      £99.99
    </div> 

  </div>
</div>


Here's a script based solution, for anyone who want that.

What's missing in it is a resize event handler, that does recalculate max height per row instead.

(function(d) {
  window.addEventListener("load", function() {
    var item = d.querySelector('.items');
    var items = item.querySelectorAll('.item__features');
    var heights = [], i = 0, css;
    for (i = 0; i < items.length; i++) {
      heights.push(parseFloat(window.getComputedStyle(items[i], null).getPropertyValue("height")));
    }
    css = ".item__features { height: " + Math.max.apply(null, heights) + "px; }" ;
    var st = d.createElement('style');
    st.type = 'text/css';
    if (st.styleSheet) {
      st.styleSheet.cssText = css
    } else {
      st.appendChild(d.createTextNode(css));
    }
    (d.head || d.getElementsByTagName('head')[0]).appendChild(st);
  }, false);
}(document));
.items {
  display: flex;
  flex-wrap: wrap;
  max-width: 1200px;
}

.item {
  display: flex;
  flex-direction: column;
  width: 25%;
  box-sizing: border-box;
  padding: 0 12px;
  margin: 24px -4px 24px 0;
}

@media (max-width: 600px) {
  .item {
    width: 50%;
  }
}

.item__heading {
  background-color: #d4d0f5;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
}

.item__content {
  flex: 1 1 auto;
  padding: 10px;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
}

.item__features {
  padding: 10px;
  border-top: 1px solid #bbbbbb;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
  background-color: #f7cbb1;
}

.item__features ul {
  margin: 0px;
}

.item__price {
  background-color: #e0f6d9;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
}
<div class="items">

  <div class="item">
    <div class="item__heading">
      Item 1
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £99.99
    </div>
  </div>


  <div class="item">
    <div class="item__heading">
      Item 2
    </div>
    <div class="item__content">
      Some content that is longer than other items on the same row and sets the height of this section as it spans many more lines than the rest of the other content sections on this row
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £69.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 3
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
        <li>feature 2</li>
        <li>feature 3</li>
      </ul>
    </div>
    <div class="item__price">
      £69.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 4
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £109.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 5
    </div>
    <div class="item__content">
      Some content that is a medium kind of length blah blah
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item__price">
      £29.99
    </div>
  </div>

  <div class="item">
    <div class="item__heading">
      Item 6
    </div>
    <div class="item__content">
      Some content that is not that long
    </div>
    <div class="item__features">
      <ul>
        <li>feature 1</li>
        <li>feature 2</li>
      </ul>
    </div>
    <div class="item__price">
      £99.99
    </div>
  </div>

</div>

@Russell Winborn 2017-05-24 19:38:38

is it possible to preserve the space between the item cards?

@Russell Winborn 2017-05-24 19:46:09

actually, a bit of left/right margin and tweaking the widths achieves that - thanks for your answer!

@Ason 2017-05-24 19:55:31

@RussellWinborn I updated my answer to show how-to. Marked changed rules in the CSS with a /* updated */. The margin-right when there is less than 4 in a group might need some tweaking.

@Russell Winborn 2017-05-24 13:58:23

I am providing an answer to my own question - but will hold off accepting it in case anyone else comes up with something better as this answer will no doubt break some accessibility rules and is bordering on the impossible to provide a graceful fallback for browsers that do not support CSS Grid but...

If you split the HTML content items up in to blocks of four you can achieve the desired outcome with the CSS Grid style rules, no JavaScript required

.items {
  max-width: 1200px;
}

.item__heading {
  margin-top: 30px;
  background-color: #d4d0f5;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
}

.item__content {
  padding: 10px;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
}

.item__features {
  padding: 10px;
  border-top: 1px solid #bbbbbb;
  border-left: 1px solid #bbbbbb;
  border-right: 1px solid #bbbbbb;
  background-color: #f7cbb1;
}

.item__price {
  background-color: #e0f6d9;
  padding: 10px;
  text-align: center;
  border: 1px solid #bbbbbb;
}

/* DESKTOP GRID */

/* DESKTOP COLUMN LAYOUT - 4 columns, one row */
.item-block-of-four {
  display: grid;
  grid-template-columns: 2% 22.5% 2% 22.5% 2% 22.5% 2% 22.5% 2%;
}
.item-1 {
  grid-column-start: 2;
  grid-column-end: 3;
}
.item-2 {
  grid-column-start: 4;
  grid-column-end: 5;
}
.item-3 {
  grid-column-start: 6;
  grid-column-end: 7;
}
.item-4 {
  grid-column-start: 8;
  grid-column-end: 9;
}

/* ROW LAYOUT - one row for all 4 items */
.item__heading {
  grid-row-start: 1;
  grid-row-end: 2;
}
.item__content {
  grid-row-start: 2;
  grid-row-end: 3;
}
.item__features {
  grid-row-start: 3;
  grid-row-end: 4;
}
.item__price {
  grid-row-start: 4;
  grid-row-end: 5;
}

/* MOBILE GRID */

@media (max-width: 600px) {
  /* MOBILE COLUMN LAYOUT - 2 columns 2 rows */
  .item-block-of-four {
    display: grid;
    grid-template-columns: 6% 41% 6% 41% 6%;
  }
  .item-1, .item-3 {
    grid-column-start: 2;
    grid-column-end: 3;
  }
  .item-2, .item-4 {
    grid-column-start: 4;
    grid-column-end: 5;
  }

  /* MOBILE ROW LAYOUT - two sets of rows */

  /* first row set */
  .item-1.item__heading, .item-2.item__heading {
    grid-row-start: 1;
    grid-row-end: 2;
  }

  .item-1.item__content, .item-2.item__content {
    grid-row-start: 2;
    grid-row-end: 3;
  }

  .item-1.item__features, .item-2.item__features {
    grid-row-start: 3;
    grid-row-end: 4;
  }

  .item-1.item__price, .item-2.item__price {
    grid-row-start: 4;
    grid-row-end: 5;
  }

  /* second row set */
  .item-3.item__heading, .item-4.item__heading {
    grid-row-start: 6;
    grid-row-end: 7;
  }

  .item-3.item__content, .item-4.item__content {
    grid-row-start: 7;
    grid-row-end: 8;
  }

  .item-3.item__features, .item-4.item__features {
    grid-row-start: 8;
    grid-row-end: 9;
  }

  .item-3.item__price, .item-4.item__price {
    grid-row-start: 9;
    grid-row-end: 10;
  }
}
<div class="items">

  <div class="item-block-of-four">

    <div class="item-1 item__heading">
      Item 1
    </div>
    <div class="item-2 item__heading">
      Item 2
    </div>
    <div class="item-3 item__heading">
      Item 3
    </div>
    <div class="item-4 item__heading">
      Item 4
    </div>

    <div class="item-1 item__content">
      Some content that is not that long
    </div>
    <div class="item-2 item__content">
      Some content that is longer than other items on the same row and sets the height of this section as it spans many more lines than the rest of the other content sections on this row
    </div>
    <div class="item-3 item__content">
      Some content that is not that long
    </div>
    <div class="item-4 item__content">
      Some content that is not that long
    </div>

    <div class="item-1 item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item-2 item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item-3 item__features">
      <ul>
        <li>feature 1</li>
        <li>feature 2</li>
        <li>feature 3</li>
      </ul>
    </div>
    <div class="item-4 item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>

    <div class="item-1 item__price">
      £99.99
    </div>
    <div class="item-2 item__price">
      £69.99
    </div>
    <div class="item-3 item__price">
      £69.99
    </div>
    <div class="item-4 item__price">
      £109.99
    </div>

  </div><!-- /item-block-of-four -->

  <div class="item-block-of-four">

    <div class="item-1 item__heading">
      Item 5
    </div>
    <div class="item-2 item__heading">
      Item 6
    </div>

    <div class="item-1 item__content">
      Some content that is a medium kind of length blah blah
    </div>
    <div class="item-2 item__content">
      Some content that is not that long
    </div>

    <div class="item-1 item__features">
      <ul>
        <li>feature 1</li>
      </ul>
    </div>
    <div class="item-2 item__features">
      <ul>
        <li>feature 1</li>
        <li>feature 2</li>
      </ul>
    </div>

    <div class="item-1 item__price">
      £29.99
    </div>
    <div class="item-2 item__price">
      £99.99
    </div>

  </div><!-- /item-block-of-four -->

</div><!-- /items-container -->

Related Questions

Sponsored Content

3 Answered Questions

[SOLVED] Cannot display HTML string

3 Answered Questions

[SOLVED] Why is CSS Grid row height different in Safari?

  • 2019-03-25 22:34:30
  • kisabelle
  • 2210 View
  • 7 Score
  • 3 Answer
  • Tags:   css safari css-grid

1 Answered Questions

css grid to fill 100vh without affecting row height

1 Answered Questions

[SOLVED] Grid inside flex with fixed-width left and right sidebars

1 Answered Questions

CSS Grid Row Height

  • 2018-08-27 17:33:32
  • JoethaCoder
  • 109 View
  • 0 Score
  • 1 Answer
  • Tags:   css css-grid

1 Answered Questions

[SOLVED] CSS Grid: Border for incomplete rows

  • 2018-03-04 17:48:31
  • tobias47n9e
  • 555 View
  • 5 Score
  • 1 Answer
  • Tags:   html css css-grid

1 Answered Questions

[SOLVED] CSS only solution to set "same height" row sections on a responsive grid

2 Answered Questions

3 Answered Questions

[SOLVED] Set height of CSS flex elements to the same amount?

1 Answered Questions

[SOLVED] GridView Lines Not Showing up in IE

  • 2012-02-23 22:22:46
  • Nick Manojlovic
  • 8788 View
  • 5 Score
  • 1 Answer
  • Tags:   asp.net css

Sponsored Content