CSS card UI: build a hover card with the filter property

Build a card with nothing but HTML and CSS: sketch the structure first, then write the styles, including the filter property that keeps the image black and white until the visitor hovers it.

CSS card UI: build a hover card with the filter property
Article updated

This article was updated on 2 November 2022 with the addition of a YouTube video.

In this tutorial I will show you how to build a card. A card shows the reader an excerpt from an article together with its image, to make them want to click through and read it. It is built with HTML and CSS only. I wanted to use a CSS property that does not get much use, filter, which applies a treatment to an image, and I added a transition to give it some life.

See the Pen
Card UI CSS
by Damien Flandrin (@dam62500)
on CodePen.

Step 1 – Sketch the structure and write the HTML

Before writing a line of HTML, picture the structure. When you are working from a mock-up, take the time to think through the structure of your site and of your CSS modules. Sketch it on a sheet of paper if that helps. Well-structured HTML makes everything that follows easier!

schema-card.png

html
<div class="card">
    <a href="https://www.gekkode.com/en/blog/post/#">
        <!-- Image à la une -->
        <div class="card-image"><img src="https://www.gekkode.com/wp-content/uploads/2021/08/orange.jpeg" alt="Orange" /></div>
        <!-- Fin de l'image à la une -->

<!-- Corp de notre carte -->
        <div class="card-body">

<!-- Date de publication de l'article-->
            <div class="card-date">
                <time>20 Novembre 1992</time>
            </div>

<!-- Titre de l'article -->
            <div class="card-title">
                <h3>Lorem Ipsum</h3>
            </div>
            <!-- Extrait de l'article -->
            <div class="card-excerpt">
                <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam voluptatibus autem consectetur voluptate facere at, omnis ab optio placeat officiis! Animi modi harum enim quia veniam consectetur unde autem inventore.</p>
            </div>

</div>
        <!-- Fin du corp de notre carte -->
    </a>
</div>

Step 2 – Set the base rules and style the module

Once the HTML skeleton is in place, we move on to the CSS, taking each part of the sketch in turn.

The same styles work just as well in SCSS, with a variable for the shadow colour and nesting for the hover state. That does add a compilation step: see compiling SCSS in a Webpack build.

.card

css
.card {
  width: 400px; /*1*/
  margin: 0px auto; /*2*/
  background-color: white; /*3*/
  box-shadow: 0px 5px 20px #999; /*4*/
}
.card a { /*5*/
  color: #333;
  text-decoration: none;
}
.card:hover .card-image img { /*6*/
  width: 160%;
  filter: grayscale(0);
}
  1. We give the card a fixed width.
  2. We centre it with margin: 0px auto, make sure the element really is display: block.
  3. We make it white; I put a slightly darker background on the body so the card stands out from the page.
  4. We add a soft shadow so the card looks like it sits above the page.
  5. We set the base style of the links inside the .card module.
  6. For the movement mentioned earlier, we tell the browser that as soon as the visitor hovers the card, the image grows through width and gets its colours back, since a black-and-white filter is applied by default on .card-image.

.card-image

css
.card-image {
  height: 250px;/*1*/
  position: relative;/*2*/
  overflow: hidden;/*3*/
}
.card-image img {
  width: 150%;/*4*/
    /*5 - Centring method based on the size of the image */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  filter: grayscale(1);/*6*/
    /*7 - Transition */
  transition-property: filter width;
  transition-duration: .3s;
  transition-timing-function: ease;
}
  1. We fix the height of the block that holds the image, that fixed size keeps every image the same when several cards sit next to each other.
  2. We set the element to relative, because it will contain absolute elements.
  3. We hide anything that overflows the block.
  4. We set the image size to 100%. You can raise that default to suit your needs, but do not use images narrower than 400px, and keep the width/height ratio so you do not end up with empty space.
  5. This is the way to centre an image against its own size and its parent. The idea is to put its origin at the centre of .card-image, then use transform to shift it back by half its own size so it sits exactly in the middle of .card-image.
  6. The filter property applies a filter to an image, here we want a black-and-white grayscale filter by default, so that the colours can come back when the user hovers the card.
  7. For the movement, we put a 300ms transition on both filter and width.

.card-body

css
.card-body {
    text-align:center;  /*1*/
  padding: 15px 20px; /*2*/
  box-sizing: border-box; /*3*/
}
  1. We align everything inside .card-body.
  2. To stop the content touching the edges, we add breathing room with the padding property.
  3. Then, if it is not already the default, we set box-sizing to border-box so the padding is counted inside the block rather than added to it.

Typography

css
.card-date {
  font-family: 'Source Sans Pro', sans-serif;
}

.card-title, .card-excerpt {
   font-family: 'Playfair Display', serif;
}

.card-date, .card-title {
  text-align:center;
  text-transform:uppercase;
  font-weight: bold;
}

.card-date, .card-excerpt {
  color: #777;
}

As you would expect, this is where the text style is set: I used Source Sans Pro as the sans-serif and Playfair Display as the serif, plus a few formatting tweaks for the text.

CSS

Damien Flandrin Web developer since 2010, creator of Gekkode and Email Impact. Every article is tested on a real project before publication. Contact
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.