How can I make a responsive CSS layout with cards, keeping the top and bottom sections of the cards the same height as the tallest in the row

I have a layout where I want to display multiple “cards.” Each card will consist of 3 sections laid out vertically. The top section will contain text that will be different lengths for different cards, and could end up being long enough to wrap to 3 lines. The middle section will contain a div that can contain various other elements that will be different heights for each card. And the bottom section will either be empty or have a single line of text. If none of the cards on a row have text in the bottom section, I don’t want any space to be taken up by them. I want the cards to be responsive, so that if they don’t all fit side-by-side, it will wrap down and put them in a grid pattern, and if the screen is small enough, the cards will all just be displayed in a single column, top to bottom. The part I’m struggling with is that I want all of the cards that are on the same row to have the same height as the tallest card on that row, and I want the top and bottom of each section of each card on the same row to be aligned.

This is essentially what I want it to look like on a larger screen:

And on a smaller screen:

My first thought was that a CSS grid would work. I could put the top part of each card on the first row and the middle part on the second row, and the bottom part on the third row. That will make it easy to get the heights the same and keep everything aligned. But I can’t figure out how to make that responsive the way I want. Using auto-fit is going to wrap a single cell at a time, which is only part of a card.

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(133px, 1fr));
  gap: 30px 5px;
  align-items: end;
  border: 1px solid grey;
  margin-bottom: 20px;
}

.middle {
  align-self: start;
  background-color: lightblue;
}

.a2 {
  height: 50px;
}

.b2 {
  height: 30px;
}

.c2 {
  height: 15px;
}

.optional {
  font-size: .6rem;
}

.pseudo-screen-large {
  width: 500px;
}

.pseudo-screen-medium {
  width: 300px;
}

.pseudo-screen-small {
  width: 150px;
}
On a large screen everything looks good:
<div class="pseudo-screen-large">
  <div class="container">
    <span>A1 - Short Text</span>
    <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
    <span>C1 - Medium Text that is going to wrap to 2 lines</span>
    <div class="middle a2">A2</div>
    <div class="middle b2">B2</div>
    <div class="middle c2">C2</div>
    <div class="optional">*Explanatory Text</div>
    <div class="optional"></div>
    <div class="optional">*Explanatory Text</div>
  </div>
</div>

Even with no optional text, we get some extra whitespace because of the row gap. I haven't tried to fix that yet, because of the other problems.
<div class="pseudo-screen-large">
  <div class="container">
    <span>A1 - Short Text</span>
    <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
    <span>C1 - Medium Text that is going to wrap to 2 lines</span>
    <div class="middle a2">A2</div>
    <div class="middle b2">B2</div>
    <div class="middle c2">C2</div>
    <div class="optional"></div>
    <div class="optional"></div>
    <div class="optional"></div>
  </div>
</div>

But once we start wrapping on smaller screens, there's immediately a problem:
<div class="pseudo-screen-medium">
  <div class="container">
    <span>A1 - Short Text</span>
    <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
    <span>C1 - Medium Text that is going to wrap to 2 lines</span>
    <div class="middle a2">A2</div>
    <div class="middle b2">B2</div>
    <div class="middle c2">C2</div>
    <div class="optional">*Explanatory Text</div>
    <div class="optional hide">*Explanatory Text</div>
    <div class="optional">*Explanatory Text</div>
  </div>
</div>

<div class="pseudo-screen-small">
  <div class="container">
    <span>A1 - Short Text</span>
    <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
    <span>C1 - Medium Text that is going to wrap to 2 lines</span>
    <div class="middle a2">A2</div>
    <div class="middle b2">B2</div>
    <div class="middle c2">C2</div>
    <div class="optional">*Explanatory Text</div>
    <div class="optional hide">*Explanatory Text</div>
    <div class="optional">*Explanatory Text</div>
  </div>
</div>

I could make each card its own cell in the grid and then use a flexbox or another grid within each card cell for the parts of the card, but then there’s no relationship between the tops of the cards on the same row, to keep them aligned.

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(133px, 1fr));
  gap: 30px 5px;
  border: 1px solid grey;
  margin-bottom: 30px;
}

.card {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.card-grid {
  display: grid;
  align-items: end;
}

.middle {
  background-color: lightblue;
}

.card-grid .middle {
  align-self: start;
}

.a2 {
  height: 50px;
}

.b2 {
  height: 30px;
}

.c2 {
  height: 15px;
}

.optional {
  font-size: .6rem;
}

.pseudo-screen-large {
  width: 500px;
}

.pseudo-screen-medium {
  width: 300px;
}

.pseudo-screen-small {
  width: 150px;
}
Here we get the wrapping how I want, but the vertical alignment of the cards is all messed up:
<div class="pseudo-screen-medium">
  <div class="container">
    <div class="card">
      <span>A1 - Short Text</span>
      <div class="middle a2">A2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle b2">B2</div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle c2">C2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
  </div>
</div>

Using a subgrid instead of a flexbox for the cards results in the same:
<div class="pseudo-screen-medium">
  <div class="container">
    <div class="card-grid">
      <span>A1 - Short Text</span>
      <div class="middle a2">A2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card-grid">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle b2">B2</div>
      <div class="optional"></div>
    </div>
    <div class="card-grid">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle c2">C2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
  </div>
</div>

Since I want the wrapping at the card level, a flexbox also makes sense. However that results in the same problem as grid with each card being in its own cell.

.container {
  display: flex;
  flex-wrap: wrap;
  gap: 20px 5px;
  margin-bottom: 20px;
}

.card {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  width: 161px;
  border: 1px solid grey;
}

.middle {
  background-color: lightblue;
}

.a2 {
  height: 50px;
}

.b2 {
  height: 30px;
}

.c2 {
  height: 15px;
}

.optional {
  font-size: .6rem;
}

.hide {
  visibility: hidden;
}

.pseudo-screen-large {
  width: 500px;
}

.pseudo-screen-medium {
  width: 350px;
}

.pseudo-screen-small {
  width: 150px;
}
Again, everything wraps well, but the vertical alignment is all messed up:
<div class="pseudo-screen-medium">
  <div class="container">
    <div class="card">
      <span>A1 - Short Text</span>
      <div class="middle a2">A2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle b2">B2</div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle c2">C2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
  </div>
</div>

I could set the height on all of the middle and bottom sections to be the height of the largest that exists for each section. But then when a card is on a row where the largest isn’t, there’s a lot of extra unwanted whitespace.

.container {
  display: flex;
  flex-wrap: wrap;
  gap: 30px 5px;
  border: 1px solid grey;
  margin-bottom: 20px;
}

.card {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  width: 161px;
}

.middle-container {
  height: 50px;
}

.middle {
  background-color: lightblue;
}

.a2 {
  height: 50px;
}

.b2 {
  height: 30px;
}

.c2 {
  height: 15px;
}

.optional {
  font-size: .6rem;
  height: 11px;
}

.hide {
  visibility: hidden;
}

.pseudo-screen-large {
  width: 500px;
}

.pseudo-screen-medium {
  width: 350px;
}

.pseudo-screen-small {
  width: 150px;
}
Here everything wraps correctly, and the vertical alignment is pretty good. But we have a huge gap below C2 & D2, and a smaller gap below E2, neither of which I want.
<div class="pseudo-screen-medium">
  <div class="container">
    <div class="card">
      <span>A1 - Short Text</span>
      <div class="middle-container">
        <div class="middle a2">A2</div>
      </div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle-container">
        <div class="middle b2">B2</div>
      </div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle-container">
        <div class="middle c2">C2</div>
      </div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>D1 - Short Text</span>
      <div class="middle-container">
        <div class="middle c2">D2</div>
      </div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>E1 - Short Text</span>
      <div class="middle-container">
        <div class="middle a2">E2</div>
      </div>
      <div class="optional"></div>
    </div>
  </div>
</div>

The only thing I’ve found that works is to use a grid like in my first snippet above, and instead of relying on auto-fit to make it responsive, I would use media queries with grid-template-areas to explicitly layout where each cell should be placed. But this would require using a media query for a screen the width of a single card, another media query for a screen the width of 2 cards, etc. And it would require assigning each card its own grid-area name. This is possible, and could probably be simplified with a Sass mixin, but it still seems pretty heavy-handed, so I’m wondering if there’s a simpler way.

2

Using actual subgrid seems to solve your issue as covered in Align Child Elements Different Blocks

* {
  margin: 0
}

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(133px, 1fr));
  gap: 5px;
  border: 1px solid grey;
  margin-bottom: 30px;
}

.card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  border: 1px solid red;
}

.middle {
  background-color: lightblue;
}

.optional {
  font-size: .6rem;
}

.pseudo-screen-large {
  width: 600px;
}

.pseudo-screen-medium {
  width: 300px;
}

.pseudo-screen-small {
  width: 150px;
}
Small:
<div class="pseudo-screen-small">
  <div class="container">
    <div class="card">
      <span>A1 - Short Text</span>
      <div class="middle a2">A2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle b2">B2</div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle c2">C2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
  </div>
</div>

Medium:
<div class="pseudo-screen-medium">
  <div class="container">
    <div class="card">
      <span>A1 - Short Text</span>
      <div class="middle a2">A2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle b2">B2</div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle c2">C2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
  </div>
</div>

Large:
<div class="pseudo-screen-large">
  <div class="container">
    <div class="card">
      <span>A1 - Short Text</span>
      <div class="middle a2">A2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
    <div class="card">
      <span>B1 - Longer Text that is going to wrap to 3 lines because it is really long</span>
      <div class="middle b2">B2</div>
      <div class="optional"></div>
    </div>
    <div class="card">
      <span>C1 - Medium Text that is going to wrap to 2 lines</span>
      <div class="middle c2">C2</div>
      <div class="optional">*Explanatory Text</div>
    </div>
  </div>
</div>

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật