No Time Dad

A blog about web development written by a busy dad

Responsive Blog Card Using HTML and CSS

Intro

Inspired by some of the article cards on css-tricks I thought it would be fun to try and build my own version of the humble blog post card. The card will have a swanky header image section using a random image from unsplash, as well as main content section. The main content section will contain the blog post title, a blog post description, and a footer that will have the authors name and some metadata.

Here is what we’ll be making:

Computer
Awesome blog post you definitely want to read
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Leo a diam sollicitudin tempor id. Id neque aliquam vestibulum morbi blandit cursus risus at ultrices. Suspendisse faucibus interdum posuere lorem ipsum dolor sit. Maecenas pharetra convallis posuere morbi leo.

Building the Card

We’ll start with a basic structure and build out each section of our card from there. All good cards have a box-shadow selector that gives the card it’s outline and border shape. You can add an actual border selector but it is much more common to see a box-shadow for cards. Most cards will also have rounded corners via the border-radius selector. We’ll also want our cards to all the same height because blog cards are usually part of a css grid that will align each card next to one another. Having one small card and one large card next to each other will look a little wonky.

Basic Scaffold

So, we’ll put together the basic html scaffolding of our card, as well as a few basic selectors in our css file. The image and title should be clickable links that go to the blog post. You can use any image you like, but I’d recommend using one that is already close to 400px wide so it doesn’t look too distorted. Since the image is a link, adding a hover effect would also be be a good idea.

After the image section we’ll have the card content section. This section will include the blog post title, description, and some metadata. We can ensure the height is consitent by adding height and max-height properties. We’ll also use a flexbox column here to make content alignment easier.

.Card {
  /* We'll use these values in a few places, so custom properties inside the parent selector makes sense. */
  --card-max-width: 400px;
  --shared-opactiy: 0.7;
  background-color: white;
  border-radius: 0.5rem;
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  max-width: var(--card-max-width);
}

.Card__Image {
  width: var(--card-max-width);
  height: 200px;
  /* Rounding the top corners for a nice clean look */
  border-top-right-radius: 0.5rem;
  border-top-left-radius: 0.5rem;
}

.Card__Content {
  display: flex;
  flex-direction: column;
  padding: 1rem;
  height: 250px;
  max-height: 250px;
}
<div class="Card">
  <a href="#blog-post">
    <img src="computer.jpg" alt="Computer" class="Card__Image" />
  </a>
  <div class="Card__Content">
    <a href="#blog-post" class="Content__Title">
      <div class="Title__Text">Blog post title</div>
    </a>
    <div class="Content__Description">Description</div>
    <div class="Content__Footer">Footer</div>
  </div>
</div>
Computer
Blog post title
Description
Footer
Title Section

For the card title we’ll want want larger text to draw in the user’s attention. A hover effect would be nice too since it is a link. Another important thing to consider here is that blog post titles can be short or long. We need to account for any length title and not have it impact the overall height of the card or the content beneath the title. The way we’ll do that is by implmentlenting a line clamp. Our line clamp basically says that any title longer than 2 lines will be clipped. This will ensure consistent spacing throughout the card.

Clamping lines is a little tricky and things can go wrong if you are adding padding the element you are clamping. Basically, if you have padding or margin on the element you are clamping then the clamp will not work and you’ll see more text than you want. One way to avoid that issue is to use a container element that will have the padding applied. You can then clamp the child element and it should work as expected.

We’ll update our html slightly to include a super long title so you can see the line-clamp in action. We’ll also add two new selectors to our existing css.

...
.Content__Title {
  color: black;
  text-decoration: none;
  font-size: 20px;
  font-weight: 700;
  padding-bottom: 0.75rem;
}
.Content__Title:hover {
  opacity: var(--shared-opactiy);
}

.Title__Text {
  display: -webkit-box;
  /* Allow at most 2 lines to be displayed */
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
...
<div class="Card">
  <a href="#blog-post">
    <img src="computer.jpg" alt="Computer" class="Card__Image" />
  </a>
  <div class="Card__Content">
    <a href="#blog-post" class="Content__Title">
      <div class="Title__Text">
        Super long blog post title that will almost certainly span way more than
        two lines and will need to be cut off.
      </div>
    </a>
    <div class="Content__Description">Description</div>
    <div class="Content__Footer">Footer</div>
  </div>
</div>
Description Section

The content description is where the blog post summary will go. Basically, a blurb of text describing the post and inviting the user to read the post. Similar to the post title, we will want to be sure to limit how many lines are displayed here. This block of text can sometimes get big and we want to be sure it doesn’t mess up our spacing, so we’ll clamp the description section to 4 lines.

Our html will mostly be the same again, but we’ll add some long description text to show how the line-clamping works. We’ll add two new selectors to our css called Content__Description (the container used for padding), and Description__Text (used for the actual description text for the blog post).

...
.Content__Description {
  padding-bottom: 1rem;
}

.Description__Text {
  opacity: var(--shared-opacity);
  display: -webkit-box;
  /* Allow at most 4 lines to be displayed */
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
...
<div class="Card">
  <a href="#blog-post">
    <img src="computer.jpg" alt="Computer" class="Card__Image" />
  </a>
  <div class="Card__Content">
    <a href="#blog-post" class="Content__Title">
      <div class="Title__Text">
        Super long blog post title that will almost certainly span way more than
        two lines and will need to be cut off.
      </div>
    </a>
    <div class="Content__Description">
      <div class="Description__Text">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Leo a diam
        sollicitudin tempor id. Id neque aliquam vestibulum morbi blandit cursus
        risus at ultrices. Suspendisse faucibus interdum posuere lorem ipsum
        dolor sit. Maecenas pharetra convallis posuere morbi leo.
      </div>
    </div>
    <div class="Content__Footer">Footer</div>
  </div>
</div>
Computer
Super long blog post title that will almost certainly span way more than two lines and will need to be cut off.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Leo a diam sollicitudin tempor id. Id neque aliquam vestibulum morbi blandit cursus risus at ultrices. Suspendisse faucibus interdum posuere lorem ipsum dolor sit. Maecenas pharetra convallis posuere morbi leo.
Footer
Footer Section

The last part of our blog post card to take a look at is the footer. As mentioned above, the footer will contain some metadata for the blog post itself. We’ll use flexbox here to help with alignment, and a media query to slightly change how the footer looks on smaller screens.

For the purposes of our demonstration here I’m just going to use a grey circle for the author’s picture, but feel free to drop in any photo or avatar you’d like. I’ll also be hiding the avatar on smaller screen using a media query because I think the card starts to looks cluttered when the viewport gets smaller. Our footer html block will be updated slightly to include the new footer content, and we’ll add a few new selectors to our css.

...
.Content__Footer {
  /* Force the footer to the bottom */
  margin-top: auto;
  /* Use flexbox to help with alignment */
  display: flex;
  align-items: center;
}

.Footer__Image {
  /* Hide the avatar on small screens */
  display: none;
}

.Footer__Meta {
  /* Use flexbox again to align the meta section in a column */
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.Meta__Author {
  text-decoration: none;
  color: black;
  font-weight: 600;
}
.Meta__Author:hover {
  opacity: var(--shared-opactiy);
}

.Meta__Date {
  opacity: var(--shared-opactiy);
  font-size: 14px;
}

@media (min-width: 375px) {
  .Footer__Image {
    /* Display the footer image on larger screens */
    display: block;
    /* The background color here is just a placeholder to be replaced with an actual avatar */
    background-color: lightgrey;
    border-radius: 100%;
    padding: 1.5rem;
  }
  .Footer__Image:hover {
    opacity: var(--shared-opactiy);
  }
  .Footer__Meta {
    padding-left: 0.5rem;
  }
}
...
<div class="Card">
  <a href="#blog-post">
    <img src="computer.jpg" alt="Computer" class="Card__Image" />
  </a>
  <div class="Card__Content">
    <a href="#blog-post" class="Content__Title">
      <div class="Title__Text">
        Super long blog post title that will almost certainly span way more than
        two lines and will need to be cut off.
      </div>
    </a>
    <div class="Content__Description">
      <div class="Description__Text">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Leo a diam
        sollicitudin tempor id. Id neque aliquam vestibulum morbi blandit cursus
        risus at ultrices. Suspendisse faucibus interdum posuere lorem ipsum
        dolor sit. Maecenas pharetra convallis posuere morbi leo.
      </div>
    </div>
    <div class="Content__Footer">
      <a href="#author" class="Footer__Image"></a>
      <div class="Footer__Meta">
        <a href="#author" class="Meta__Author">Tom Smith</a>
        <div class="Meta__Date">Apr 04, 2021</div>
      </div>
    </div>
  </div>
</div>
Computer
Super long blog post title that will almost certainly span way more than two lines and will need to be cut off.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Leo a diam sollicitudin tempor id. Id neque aliquam vestibulum morbi blandit cursus risus at ultrices. Suspendisse faucibus interdum posuere lorem ipsum dolor sit. Maecenas pharetra convallis posuere morbi leo.

Thoughts

Overall, the blog post card was fun to build. The line clamping was a little annoying, so I might spend some more time in the future looking into other ways to do that. The container trick seemed to work ok for line clamping but it still feels a little brittle for some reason. Also, -webkit-line-clamp has zero support in IE (if you care about that). The blog post cards looks really nice in a grid, so I highly recommend implementing them that way (demo incoming).

Final Code

Remember to use some version of a css reset with this demo.

.Card {
  --card-max-width: 400px;
  --shared-opactiy: 0.7;
  background-color: white;
  border-radius: 0.5rem;
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  max-width: var(--card-max-width);
}

.Card__Image {
  width: var(--card-max-width);
  height: 200px;
  border-top-right-radius: 0.5rem;
  border-top-left-radius: 0.5rem;
}
.Card__Image:hover {
  opacity: var(--shared-opactiy);
}

.Card__Content {
  display: flex;
  flex-direction: column;
  padding: 1rem;
  height: 270px;
  max-height: 275px;
}

.Content__Title {
  color: black;
  text-decoration: none;
  font-size: 20px;
  font-weight: 700;
  padding-bottom: 0.75rem;
}
.Content__Title:hover {
  opacity: var(--shared-opactiy);
}

.Title__Text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.Content__Description {
  padding-bottom: 1rem;
}

.Description__Text {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.Content__Footer {
  margin-top: auto;
  display: flex;
  align-items: center;
}

.Footer__Image {
  display: none;
}

.Footer__Meta {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.Meta__Author {
  text-decoration: none;
  color: black;
  font-weight: 600;
}
.Meta__Author:hover {
  opacity: var(--shared-opactiy);
}

.Meta__Date {
  opacity: var(--shared-opactiy);
  font-size: 14px;
}

@media (min-width: 375px) {
  .Footer__Image {
    display: block;
    background-color: lightgrey;
    border-radius: 100%;
    padding: 1.5rem;
  }
  .Footer__Image:hover {
    opacity: var(--shared-opactiy);
  }
  .Footer__Meta {
    padding-left: 0.5rem;
  }
}
<div class="Card">
  <a href="#blog-post">
    <img src="computer.jpg" alt="Computer" class="Card__Image" />
  </a>
  <div class="Card__Content">
    <a href="#blog-post" class="Content__Title">
      <div class="Title__Text">
        Super long blog post title that will almost certainly span way more than
        two lines and will need to be cut off.
      </div>
    </a>
    <div class="Content__Description">
      <div class="Description__Text">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
        tempor incididunt ut labore et dolore magna aliqua. Leo a diam
        sollicitudin tempor id. Id neque aliquam vestibulum morbi blandit cursus
        risus at ultrices. Suspendisse faucibus interdum posuere lorem ipsum
        dolor sit. Maecenas pharetra convallis posuere morbi leo.
      </div>
    </div>
    <div class="Content__Footer">
      <a href="#author" class="Footer__Image"></a>
      <div class="Footer__Meta">
        <a href="#author" class="Meta__Author">Tom Smith</a>
        <div class="Meta__Date">Apr 04, 2021</div>
      </div>
    </div>
  </div>
</div>