4

I'm trying to make a card component in CSS, I thought it would be straightforward, but I'm struggling with CSS grid properties. Also, I can't use flexbox in that case, for reasons not worth explaining :)

The layout is dead simple: an image on the left and some text on the right.

<div class="card">
  <figure card="card__img">
    <img src="https://via.placeholder.com/600" width="600" height="600">
  </figure>
  <p class="card__txt">Lorem ipsum tempus fugit.</p>
</div>

Here's the tricky part: the image is optional, and when present, it should not be wider than 50%.

When I put a max-width to the image, then the text doesn't expand if the image is absent.

Here's what I have at the moment:

.card {
  display: grid;
  column-gap: 1em;
  grid-template-columns: minmax(0, 50%) 1fr;
}

I tried to mess width grid-template-columns, but I'm not familiar enought yet with it.


You can see it in action on this Codepen: https://codepen.io/tcharlss/pen/gVvOjY

And here's the code snippet:

/* Layout stuff I'm strugling width*/

.card {
  display: grid;
  column-gap: 1em;
  grid-template-columns: minmax(0, 50%) 1fr;
}

/* Decoration & miscellaneous */

body { font-family: sans; }
.container {
  display: grid;
  column-gap: 1em;
  grid-template-columns: repeat(auto-fill, 35em);
}
.container + .container {
  margin-top: 2em;
}
.explication {
  color: hsl(230, 100%, 45%);
  font-family: monospace;
  font-style: italic;
}
.card {
  border: 1px solid hsl(230, 100%, 45%);
  min-height: 18em;
}
.card__txt {
  background-color: hsl(0, 0%, 90%);
  margin: 0;
}
figure {
  margin: 0;
}
img {
  max-width: 100%;
  height: auto;
}
<div class="container">

  <div class="col">
    <p class="explication">If an image is present, it can't be wider then 50%</p>
    <div class="card">
      <figure card="card__img">
        <img src="https://via.placeholder.com/600" width="600" height="600">
      </figure>
      <p class="card__txt">Lorem ipsum tempus fugit. Aut rerum nostrum qui adipisci est aut. Est error omnis sit velit.</p>
    </div>
  </div>
  
  <div class="col">
    <p class="explication">Without image, the text should expand to fill all the available space</p>
    <div class="card">
      <p class="card__txt">Lorem ipsum tempus fugit. Aut rerum nostrum qui adipisci est aut. Est error omnis sit velit.</p>
    </div>
  </div>
  
</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
tcharlss
  • 43
  • 4

3 Answers3

2

One approach, the easiest I can think of, is to style the generic .card__text elements one way, and then use the + combinator to style the elements that follow a .card__img element differently:

/* here we use Grid layout, setting two columns
   each of 1fr (using the repeat() function): */
.card {
  display: grid;
  grid-gap: 1em;
  grid-template-columns: repeat(2, 1fr);
}

/* setting the default presentation of the
   .card__txt elements; positioning them
   in the first column, spanning two
   columns: */
.card__txt {
  grid-column: 1 / span 2;
}

/* separately styling the .card__txt elements
   that follow a .card__img element; here we
   place them in the second column: */
.card__img + .card__txt {
  grid-column: 2;
}

/* Decoration & miscellaneous */

body {
  font-family: sans;
}

.container {
  display: grid;
  column-gap: 1em;
  grid-template-columns: repeat(auto-fill, 35em);
}

.container+.container {
  margin-top: 2em;
}

.explication {
  color: hsl(230, 100%, 45%);
  font-family: monospace;
  font-style: italic;
}

.card {
  border: 1px solid hsl(230, 100%, 45%);
  min-height: 18em;
}

.card__txt {
  background-color: hsl(0, 0%, 90%);
  margin: 0;
}

figure {
  margin: 0;
}

img {
  max-width: 100%;
  height: auto;
}


/* changes */

.card {
  display: grid;
  grid-gap: 1em;
  grid-template-columns: repeat(2, 1fr);
}

.card__txt {
  grid-column: 1 / span 2;
}

.card__img+.card__txt {
  grid-column: 2;
}
<div class="container">

  <div class="col">
    <p class="explication">If an image is present, it can't be wider then 50%</p>
    <div class="card">

      <!-- Note the correction, in your posted code the following
           was written:
      <figure card="card__img">
      I corrected 'card' to 'class' -->
      <figure class="card__img">
        <img src="https://via.placeholder.com/600" width="600" height="600">
      </figure>
      <p class="card__txt">Lorem ipsum tempus fugit. Aut rerum nostrum qui adipisci est aut. Est error omnis sit velit.</p>
    </div>
  </div>

  <div class="col">
    <p class="explication">Without image, the text should expand to fill all the available space</p>
    <div class="card">
      <p class="card__txt">Lorem ipsum tempus fugit. Aut rerum nostrum qui adipisci est aut. Est error omnis sit velit.</p>
    </div>
  </div>

</div>

JS Fiddle demo.

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • 1
    Thanks a lot David. I will go with that solution, it remains simple enough and easy to understand. In my real-world case which is a little more complicated, I have to use the siblings selector, but the idea remains the same: `.card__img ~ .card__txt` – tcharlss Aug 07 '19 at 13:20
1

A solution with a combination of repeat(), auto-fit and minmax() :

.card {
  display: grid;
  column-gap: 1em;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); }

At max each items can be one fraction of the available space so 50% if there is 2 and 100% if there is one.

  • General mechanism

    If for exemple the container is 500px (with no gap) and the min value of minmax is 100px, auto-fit will create 5 explicit columns of 100px, place your items in those columns (create a new row if there are more than 5 items), and if some columns are empty collapse them (and their gaps if there is any) and finally stretch the columns with items at the most to their max value (from minmax).
  • In your exemple

    The container is 560px, with gaps of 16px so auto-fit create 4 columns of 100px (plus 16*3=48px of gaps - It can't make 5 col because it will be 564px) and collapses 2 or 3 depending if there is an image or not, and stretches your item(s)'s columns to one fraction of the available space.

I forked your pen here.

I used this CSS-Tricks article about auto-fit / auto-fill and the Firefox inspector grid tool.

1

I think the answer by @DavidThomas is as good as it gets in this particular scenario.

There doesn't seem to be anything better in the current iteration of Grid (Level 1).

For the sake of clarity, here's a description of the problem:

You have defined a two-column grid:

.card {
  display: grid;
  column-gap: 1em;
  grid-template-columns: minmax(0, 50%) 1fr;
}

By using grid-template-columns you've created explicit columns. Such columns are built into the grid from the start and cannot be removed. Occupied or unoccupied, these columns exist.

That's why the text in your example without the image doesn't expand: the first column is holding its space, which is defined as minmax(0, 50%).

One may think that the column would default to the min value (0) when unoccupied, thus allowing the second column (1fr) to expand across the container.

But this is not the case. The minmax() function defaults to the max value. That's why you see 50% empty space in your example without the image.


Aside from the selector wizardry proposed by @DavidThomas, the best approach to a solution I can think of involves switching from grid-template-columns to grid-auto-columns.

This would take you from an explicit grid (columns always exist) to an implicit grid (columns exist as necessary). This method also removes the column gap.

But there are many obstacles to overcome with this approach, including:

  • the order of your elements would have to switch (the image would have to come after your text because otherwise minmax(0, 50%) would always be the first column; the order property won't help because it applies to items, not columns);
  • setting the column to min-content and the item to max-width: 50% won't work either because, in this case, you're using an image, and the browser computes the column width based on the image's specified value, not the used value (demo).

However, if you want to pursue this approach, here's how it works in detail:

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • 2
    Thanks so much for the detailled explanation, it's very clear now why it didn't work. I will experiment further with `grid-auto-columns`, for the time being I think @ThomasDavid 's solution will do the job. – tcharlss Aug 07 '19 at 13:27