Style a Card Layout with CSS
Tags: tutorial, beginners, css, webdev
Published: 2020-03-21 15:37:27

In this post, we will cover:

- consolidating global styles
- the `:first-child` pseudo class*
- `display: inline-block`
- introduction to CSS grid
- how to define a dynamically resized image

This is the fourteenth post and video in a series on learning web development. [Learn more about the series and see the post schedule >](https://thinkdobecreate.com/learnwebdev)

You may want to refer back to the first lesson that created our initial card HTML, episode 6:

{% link https://dev.to/5t3ph/card-layout-in-html-473j %}

You may watch the following video or follow along with the expanded transcript that follows.

{% youtube WtQMRsYWgzM %}

[*Review the source code* >](https://github.com/5t3ph/howtowebdev/tree/master/ep14-card-css)

To begin this lesson, open the [starter project](https://github.com/5t3ph/howtowebdev-starter) you began in episode 1, or [review the source files](https://github.com/5t3ph/howtowebdev/tree/master/ep13-blog-css) from episode 13.

To begin this lesson, open our project in VSCode.

In the Terminal, type the start command `npm run start` to run the project.

Open `card-layout.html` in VSCode, and organize your screen to be split with the browser. Then update the browser url to include `/card-layout.html`.

![window split with VSCode and browser showing card-layout.html](https://dev-to-uploads.s3.amazonaws.com/i/5qit9x42iwn2ikhpq2z6.png)

Similar to the last lesson, the first thing we need to do is create a dedicated stylesheet for this page's styles since we do not wish to inherit styles from `style.css` which is what is giving us the text colors.

Create `card-layout.css`, and then in our HTML file, update the stylesheet `link` in the `<head>` from `style.css` to `card-layout.css` and save. 

```html
<link rel="stylesheet" href="card-layout.css" />
```

![layout appearance with blank stylesheet](https://dev-to-uploads.s3.amazonaws.com/i/weodxdbm2kpwpf78x7zl.png)

We're nearly back to default browser styles, except we cheated a bit in the HTML lesson and added a handful of styles directly in the HTML. That technique is called "inline styles".

Because inline styles are closest to the element, they are calculated as having a higher specificity in the CSS cascade than element or class selectors. There are ways to override this, but it's an additional rule of the cascade to be aware of.

Refer back to episode 11 if needed to learn more about the CSS cascade. 

{% link https://dev.to/5t3ph/intro-to-the-css-cascade-the-c-in-css-1kh0 %}

Let's work on moving the inline styles into our stylesheet.

Looking at the layout we created,   it seems reasonable to create two classes: `.card-row` and `.card`. We'll move the styles on the `<section>` element into `.card-row` and the styles from the `<article>` elements to `.card`.

```css
    .card-row {
      display: flex;
      justify-content: space-between;
    }
    
    .card {
      width: 320px;
      padding: 20px;
      border: 1px solid #c9c9c9;
    }
```

Then we'll replace the `style` attribute with the `class` attribute and the appropriate class:

```html
    <section class="card-row">
    
    <article class="card">
```

Be sure to save both the CSS and HTML files and you can see our appearance is unchanged from what we created in the HTML lesson.

For now, let's remove the second and third card so we can focus on the typography and `.card` styles.

Starting with typography, it would be nice to use what we already defined for the blog page, if we pretend that the blog and product layouts are pages of the same site.

So let's create one more stylesheet - `global.css`. Then open `blog-layout.css` and let's move from the `body` rule through the `p` rule to our new stylesheet. Then also get from the `.container` rule through to the `body > footer` rule.

You can view the final [global.css](https://github.com/5t3ph/howtowebdev/blob/master/ep14-card-css/src/global.css) stylesheet in the project repo.

Now, in both `blog-layout.html` and `card-layout.html`, we need to add a new stylesheet link, and it needs to be placed above the existing stylesheet link for the cascade to work optimally.

```html
    <link rel="stylesheet" href="global.css" />
    <!-- existing page stylesheet link -->
```

Following that, we need to update the markup of our `<header>` and `<footer>` to match what we did for the blog page, as you can see the spacing is not correct since they are missing the `div` with the `container` class.

```html
<header>
  <div class="container"> 
  <!-- header content -->

<!-- main -->

<footer>
  <div class="container"> 
  <!-- footer content -->
```

Then, we'll add the `container` class to the `main` element and our layout is looking much better already.

```html
<main class="container">
```

![layout with container classes added](https://dev-to-uploads.s3.amazonaws.com/i/31dfmfznod9vz1s3csux.png)

We can now turn our attention to the `.card` and first, we'll update the card styles with some other properties we've already learned about:

```css
    .card {
      /* previously added styles */
      border-radius: 7px;
      box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
    }
```

Save, and the card feels more on-trend.

![card with additional styles](https://dev-to-uploads.s3.amazonaws.com/i/afi1rh7r9in0190xc5ap.png)

Next, we'll create styles for the image. If we assume that every time a `.card` is in use it has one `img` placed at the top, then we can use the selector `.card img` to define that the rule will apply to images only within `.card`. 

However, to ensure it only ever applies to the _first_ image, we'll use the direct child selector `>` and what's called a "pseudo class" to make our full selector `.card > img:first-child`. The full meaning of this selector is now "apply these properties only to images that are a direct child of the card **and** are the first-child element within the card."

We'll start with one definition for `border-radius` which will round the top two corners but not the bottom two. 

```css
    .card > img:first-child {
      border-radius: 7px 7px 0 0;
    }
```

![card image with border radius](https://dev-to-uploads.s3.amazonaws.com/i/hlveamdqbvovkfxj0bvm.png)

Then in the HTML let's move the image below the `h3` and save. 

![card image placed below the h3](https://dev-to-uploads.s3.amazonaws.com/i/t2p1hzq0t1yorb4rbzx7.png)

You'll see that when the image no longer meets the `:first-child` pseudo class condition, it does not have rounded corners.

Move the image back to the top, and we'll add one more rule for `margin-bottom: 20px`.

Let's jazz up the card typography, and again use the `.card` qualifier prior to the typography element tag name:

```css
    .card h3 {
      color: rebeccapurple;
    }
    
    .card p {
      color: #757575; /* Lightest grey with 4.5:1 contrast over white */
      line-height: 1.5; /* Space between lines of the paragraph */
    }
```

![card typography styles](https://dev-to-uploads.s3.amazonaws.com/i/a3ot30y8341bpkyw5cwe.png)

Finally, we've arrived at the all-important "Add to Cart" link. It's common on e-commerce sites for this to have a more "button" appearance to help it stand out. We may want to have a button appearance on more than just card links, so let's create a rule for `.button`

```css
    .button {
      /* Reset text link default */
      text-decoration: none;
    
      background-color: rebeccapurple;
      color: #fff; /* white */
      padding: 0.5em 1em; /* vertical | horizontal */
      border-radius: 4px;
    }
```

Those are all properties we're familiar with, so let's save. But before we see the results, we need to add the `button` class to the `<a>` tag in the HTML, then save.

![link with button class styles applied](https://dev-to-uploads.s3.amazonaws.com/i/ia8qj637ir39gp7n2sod.png)

It looks pretty good, except it seems to be overlapping the bottom spacing of the card. Let's inspect and see what's happening.

![inspector highlighting the padding on card](https://dev-to-uploads.s3.amazonaws.com/i/f3dlqu0dqkys4e8pfoxe.png)

If we inspect the `.card`, we see that the padding seems to go into the button and our button text is resting there. This is because links are inline elements, and only their horizontal padding is added into the box model algorithm in terms of changing their position in relation to other elements.

Using inspector, we can change the `display` property to `block` which we learned is the complement to `inline`. 

![button display changed to block](https://dev-to-uploads.s3.amazonaws.com/i/aqrkx52quz20f9jll19m.png)

But that's not quite right - we don't want the button to take up the fullwidth, although it did remove the overlapping behavior.

Instead, a third available value for `display` is `inline-block` which allows the element width to behave using the `inline` algorithm, but also allows it to have both it's vertical margin and padding used in the display algorithm as well.

![demo of toggling display: inline-block](https://dev-to-uploads.s3.amazonaws.com/i/gby6yk6rbf361asy2e6p.gif)

Toggling this on and off, you can see it also increases the space above the button due to it now being pushed down by the paragraphs bottom margin.

Let's add the `display: inline-block` to our rule, and save.

We're almost done, but it would be nice for the price to stand out a bit more, so we'll create a rule with the selector `.card h3 em`, and let's place it following our `.card h3` rule for the sake of organization.

```css
    .card h3 em {
      padding: 0.25em;
      /* pale purple used for blog blockquote */
      background-color: #eddbff; 
      border-radius: 4px;
    }
```

Great, it stands out more, but what happens if we have a longer product title, say "Whizzbang Widget SuperDeluxe"? Let's update the HTML and find out.

![card with longer title](https://dev-to-uploads.s3.amazonaws.com/i/gahpi7nkftilm2fljhf8.png)

Oops! The price get's knocked down to the next line, and since it's an inline element, it's overlapping the text.

A newer `display` value is `grid` and this will let us define virtual "columns" and "rows" for our content to live in. If we define `display: grid` on the `.card h3` then a grid cell is created for the text and one for the `em` tag. 

Save, and you'll see the price is still below the title but it's now appearing as a block due to the default `grid` algorithm.

If we inspect the `h3` the inspector has added dashed lines to visualize each grid cell.

![inspector visualizing grid lines](https://dev-to-uploads.s3.amazonaws.com/i/cgw9lx3d15wz4jpdehff.png)

What we want are two columns where the price takes the width it needs but always stays to the far right of the title. We can accomplish this by adding `grid-template-columns: 1fr auto`. Each width designation with this property defines a separate column, so with two definitions, we've created two columns. 

The `fr` keyword means "fraction" and is unique to `grid` and using it requests the browser to compute the available "fraction" of space that is left to distribute to that column or row. In this case, it will take up the remaining space not needed for the price to fit. Save, and that's almost what we want.

![card h3 with columns defined](https://dev-to-uploads.s3.amazonaws.com/i/5yr4w6bx73comxmwb7zz.png)

The final property we will add to the `h3` rule is `align-items: start` which defines how the grid cell content should horizontally align in relation to each other. Save, and you'll see the `em` is now aligned to the top relative to the product title. 

![card h3 with align-items: start](https://dev-to-uploads.s3.amazonaws.com/i/mzv345fqchpo0bi5fwb0.png)

This has consequently also fixed its height by not allowing it to stretch the full height of the virtual row.

Now we just need to add back in a couple more cards, so let's copy our card HTML twice to have three cards again, and save.

![card layout with three cards](https://dev-to-uploads.s3.amazonaws.com/i/396glqrygyntq2x2s0v5.png)

The first apparent thing is that the cards are overflowing the viewable browser area! The reason is the absolute width we used on the `.card` of `320px`. Again, that was for demonstration purposes of our original HTML lesson.

We will remove the `width` on `.card` and then change `.card-row` to use `grid` for display. Then we'll setup `grid-template-columns: 32% 32% 32%`. The existing `justify-content: space-between` rule will distribute any leftover space between each card.

![card layout with width removed and grid added](https://dev-to-uploads.s3.amazonaws.com/i/fuvduy4xwfvg2979a52m.png)

But we still have overflow of the card area... why is that? The reason will not be apparent even from inspecting but is the type of thing to file away in your memory. What is happening is that the browser is able to infer the image display size, and so the image width is keeping the cards wide.

The essential rule to memorize for this scenario is: `max-width: 100%; height: auto;` which means the image should take up no more than 100% of the available space within its container, and that its height should proportionately resize. We'll add this definition to our rule for card images.

Save, and we now have 3 equal-width, nicely styled cards!

![final card layout](https://dev-to-uploads.s3.amazonaws.com/i/c5g8l0dx3modxrf3zp0n.png)

We'll learn more tricks for card layouts in our capstone project.

Here's the final result on CodePen:

{% codepen https://codepen.io/5t3ph/pen/bGdxGOV %}

_Note_: the demo is best on desktop since we haven't yet covered responsive design - but that's the topic for Episode 16!

> **Next up in Episode 15:** Browser Compatibility