DEV Community

loading...
Cover image for CSS Grid Magic: A practical card example

CSS Grid Magic: A practical card example

Morten @ home
Simplifier of complicated subjects, recovering philosopher, ballroom dancer, open source contributor, and parent.
・7 min read

This example is from my talk at An Event Apart Spring Summit where I discussed modern CSS layout modules and how to incorporate them into our practices and workflows.

A Card

A graphic very similar to this one popped up in a CSS forum I follow, along with a question: How do I make this layout using CSS?
Alt Text
The standard answer to this is to add some extra nesting <​div>s and use various layout hacks like negative margins, positioning, etc.

When I see examples like this, my first questions are always the same:

What is the simplest HTML representation of this content? Can I make this design happen using pure CSS without modifying the HTML?

This is the goal of web development after all: The complete separation of content (HTML) from its presentation (CSS).

Go to Codepen to see the complete live example.

Looking at the design, there is an image, and some text. One way of representing this in HTML is using a <​figure> wrapped around an <​img> and a <​figcaption>. Here’s what that looks like:

<figure class="card">
  <img src​="…" width="640" height="640" alt="​"/>
  <figcaption>
    <blockquote>We shape our tools (…)<​/blockquote>
    <cite>Attributed to Winston Churchill, Marshall McLuhan(…)<​/cite>
    <p><strong>Caju Gomes<​/strong>, <a href​="…">Unsplash<​/a><​/p>
  <​/figcaption>
<​/figure>
Enter fullscreen mode Exit fullscreen mode

That solves the content part. Now for the presentation.

Layered layouts

The real challenge in this design is the many layers. There’s a faux shadow, then the card itself which serves as a background image. Then there is the image, the text which overlaps the image, and the image credit.
Alt Text
Stacking elements one on top of the other using CSS is traditionally achieved through clever hacks using either positioning, negative margins, or both. However, CSS grid allows us to place any child element of the grid anywhere on the grid, including in the same cell or cells as other elements. So if we need to stack elements on top of one another like in this design, CSS grid lets us do that in a clean and controlled way.

The first step is to figure out what the grid should look like:
Alt Text
Next, mark up that grid in CSS. The <​figure> becomes the grid parent:

figure {
  display: grid;
  grid-template-columns: 3em 9em 1fr 1fr 6em 3em;
  grid-template-rows: 3em 9em 1fr 6em 3em;
}
Enter fullscreen mode Exit fullscreen mode

With the grid defined, place items on that grid. There are many ways of doing this, the easiest of which is to just position the times based on the grid line numbers. First the image:

img {
  grid-column: 4/5;
  grid-row: 3/4;
}
Enter fullscreen mode Exit fullscreen mode

Then the text which sits inside the figcaption:

figcaption {
  grid-column: 3/5;
  grid-row: 3/4;
}
Enter fullscreen mode Exit fullscreen mode

This puts the text in the correct general area of the grid, and on top of the image, but the design calls for more control over the exact placement of the text.
Alt Text
To achieve this, add a second grid for the <​figcaption> element and position the items within on that grid:
Alt Text

figcaption {
  grid-column: 3/5;
  grid-row: 3/4;
  display: grid;
  grid-template-columns: 3fr 1fr 2fr;
}
Enter fullscreen mode Exit fullscreen mode

That gives us the image and the text, but the design also calls for a background image and a faux shadow. What to do...

Pseudo elements as grid elements.

An interesting of both grid and flex is pseudo elements become first-level children of these containers. That means if you create a grid container around a single item and then define ::before and ::after pseudo elements for that item, the grid now has three grid items: the element itself and its before and after pseudo-elements, and each will be placed in its own grid cell. This opens up an interesting new possibilities, one of which can be used to solve the current layout problem.

The design calls for both a background image and a faux shadow.
Alt Text
Using pseudo elements both of these can be added in a non-destructive way that will gracefully degrade to older browsers without grid support.

The background image first. For this I’ll use ::before because it should appear before the actual card content in the DOM order. Place the new (pseudo) grid item on the existing grid, and then fill it with a background image:

figure::before {
  grid-column: 2/7;
  grid-row: 2/6;
  content: "​";
  display: block;
  background: white;
  background-image: url(…);
  background-size: cover;
}
Enter fullscreen mode Exit fullscreen mode

Next, the faux shadow. I’ll have to use ::after for this, which technically places the shadow after the card in the DOM order and therefore ends up on top of the card.

figure::after {
  grid-column: 1/6;
  grid-row: 1/5; 
  content: "​";
  display: block;
  background: #e0dde4;
}
Enter fullscreen mode Exit fullscreen mode

That’s easily solved by moving the item along the z-axis using z-index:

figure::after {
  grid-column: 1/6;
  grid-row: 1/5;
  z-index: -1;  
  content: "​";
  display: block;
  background: #e0dde4;
}
Enter fullscreen mode Exit fullscreen mode

This last part is another important feature of CSS grid. Because grid allows us to stack things on top of each other, we are effectively working in the 3rd dimension; literally layering content on top of content like a stack of cards. This layering is done based on DOM order, so the last item ends up on top of the stack. We can use the z-index property to change this order to whatever we want. In other words, with Grid we are now for the first time able to lay out content along three axes: horizontal, vertical, and depth.

Backwards compatibility

At this point there’s a good chance you’re thinking something along the lines of “Sure, this is cool, but grid doesn’t work in older browsers, so you need to provide fallback options.”

Surprisingly, the answer is no.

This setup doesn’t require any accommodations for older browsers!

You can test it for yourself. Open the example in your favorite browser, go to the developer tools, and disable the two instances of display:grid.
Alt Text
What you get is the main image followed by the text. The faux shadow and background image disappear as if by magic, and the visitor gets only the content they are looking for, styled in a way that makes sense in their environment.

The “magic” at play here is basic progressive enhancement: Take advantage of whatever capabilities the current browser has without reducing the experience for those without those capabilities.

Of course we could use @supports to detect grid support and build out custom styles for browsers without grid support. If I published this on a real website, I would wrap the ::before and ::after rules using @supports to prevent older browsers from downloading the background image. But, as you see this is not necessary unless you want to add specific fallback styles.

The standard argument against this approach is that the result of doing nothing will be different users having different experiences. From my perspective, that's an argument with little substance: We have used responsive web design since 2010, so for the past 10+ years we've been shipping websites that look different on different screens. This is no different, except the responsive axes have been expanded to include not only viewport width and height but also browser capability.
Websites should provide the same information exchange and functional experience for all people.
At the very onset of this project I defined semantic HTML, and I said I didn’t want to make any changes to the HTML to accommodate the style. Thus every browser will get semantic parsable content that makes sense. Then I use CSS to progressively enhance the experience when grid is available in the visiting browser.

So what happens to that faux shadow and background image? Inspect your code and you’ll see when grid is disabled, they each collapse to a height of 0px. Normally when we add pseudo elements using ::before and ::after we set their width and height which would result in them showing up when grid is not present, but since I’m using grid, that’s not necessary: Given no instructions, grid’s default treatment of grid items is to stretch them to fit the cell they are presented in. As a result, the faux shadow and background image automatically stretch to fill the grid cells, and collapse to a height of 0 when no grid is present.

CSS layouts are now a platform feature

For as long as I can remember, CSS layouts were always a collection of carefully crafted hacks. That time is now over. Between grid, flex, float, multicol, and table, we have all the layout features we need built into the platform to make the layouts we want without having to modify our markup or employ clever hacks. Now it's our job to realign our way of thinking about layouts to use these modules in our daily practice. I built this example to give you a brief glimpse of what is possible and how these new modules open doors we didn't even know existed before. I invite you to join me as we explore this new future of CSS layouts together.

For more examples, and talks from many of the leading voices of our industry, head on over to An Event Apart and sign up for the 2021 Spring Summit!

--

Originally published on LinkedIn.

Discussion (1)

Collapse
alexandriastech profile image
alex ➡️ web dev journey 👩🏽‍💻🔍

I'm new to learning CSS grid but this definitely makes me want to continue understanding then fundamentals and all we can do with CSS. Very important post 👍🏼