WordPress.org

WordPress Developer Blog

Building a card layout with a “hover reveal” effect

Building a card layout with a “hover reveal” effect

Clickable cards are a common interactive component. There are quite a few things to consider when creating them, and a lot depends on the context in which they’re being placed. Today, you will learn how to create them with the Grid block and add some CSS-based animation to enhance the final outcome.

Here are the general steps you will take:

  1. Create a layout with the Grid block and nested inner blocks.
  2. Register a new block style to expand the clickable surface area.
  3. Add animation to nested elements with more custom styles.
  4. Save the block layout as a Pattern.

Below is a video that captures the final “hover reveal” effect that you’ll create. If you would like to experiment with the final pattern or even a plugin while reading, here is the Grid Cards pattern and the hover-reveal-effect plugin.

Create the card layout with the Grid block

First, you’ll build the card layout and appearance with blocks. I ended up using the following blocks:

  • A Grid block with Auto layout type enabled. Also, you may want to consider setting the Advanced > HTML Element to <section>.
    • A Cover block with an image and an overlay applied. Also, you may want to consider setting the Advanced > HTML Element to <article>. This depends on how you intend to use the final pattern. Different HTML semantics can apply in different contexts.
      • A Stack block with a minimum height of 22rem and the vertical alignment type is set to Space between. These blocks are representative of our cards.
        • A Group block with a nested Paragraph block to represent a label for our card.
        • Another Group block comprised of a nested Heading with a link and Paragraph block as a brief excerpt.

Below is a screenshot of the blocks that form the final Grid Cards pattern:

You can experiment with different spacing, colors, and typography so that the inner blocks match your overall design aesthetic.

Bonus: Block renaming in list view
Consider renaming the key blocks in the List View. This is a nice touch for enhancing the editorial experience and can help clarify what the nested elements represent in each card. Once you’ve finalized your overall block configuration and want to save it as a pattern then the names will save as well.

Here is a screenshot which shows how you might rename your cards blocks.

Increase the clickable surface area

You might have noticed that only the Heading block is clickable. However, your site’s visitors—especially those browsing on mobile—may appreciate a larger clickable surface area.

Register a block style for a clickable card

WordPress doesn’t offer a native method to create such interactions. To achieve that, you would need to register a block style and add custom CSS. Add the following code to your theme’s functions.php:

add_action( 'init', 'themeslug_register_block_styles' );

function themeslug_register_block_styles() {
   register_block_style(
       'core/cover',
       array(
           'name'         => 'card--interactive',
           'label'        => __( 'Card (Interactive)', 'themeslug' ),
           'inline_style' => '
               .is-style-card--interactive {
                   position: relative;
               }
               .is-style-card--interactive :where(.wp-block-group.wp-block-group-is-layout-constrained) {
                   position: static;
               }
               .is-style-card--interactive :where(.wp-block-heading) a:after {
                   content: "";
                   inset: 0;
                   position: absolute;
                   z-index: 10;
               }
           ',
       )
   );
}

The code targets the Cover block with custom CSS and expects a nested Heading block that contains a link.

Back in the Editor, click each Cover block, assign the “Cards (Interactive)” style, and verify that the entire card is clickable.

If you’re happy with the results, you could stop here and enjoy this fully clickable card. Or, you could add a bit of pizzazz with some animation of the inner blocks.

Animate the label and the excerpt

Subtle animation can make for a delightful experience. You can extend your custom “Card (Interactive)” block with some CSS styles.

To keep things organized, let’s move the styles into a separate file and update the functions.php file:

  1. Create a new file within your theme: assets/css/blocks/core/cover–card-interactive.css.
  2. Update the register_block_style() function and enqueue this new stylesheet. Instead of the inline_style property, switch to the style_handle property and pass a registered handle that calls the external stylesheet.

Replace the previous code with the following:

add_action( 'init', 'themeslug_enqueue_block_styles' );

function themeslug_enqueue_block_styles() {
   wp_enqueue_block_style(
       'core/cover',
       array(
           'handle' => 'themeslug--card-interactive',
           'src'    => get_theme_file_uri( 'assets/css/blocks/core/cover-card-interactive.css' ),
           'path'   => get_theme_file_path( 'assets/css/blocks/core/cover-card-interactive.css' ),
       )
   );
}

add_action( 'init', 'themeslug_register_block_styles' );

function themeslug_register_block_styles() {
   register_block_style(
       'core/cover',
       array(
           'name'         => 'card--interactive',
           'label'        => __( 'Card (Interactive)', 'themeslug' ),
           'style_handle' => 'themeslug--card-interactive',
       )
   );
}

Copy the following CSS into your cover-card-interactive.css file. You’re welcome to adjust the animations to your liking:

.is-style-card--interactive {
   box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 0 2px 2px rgba(0, 0, 0, 0.1), 0 4px 4px rgba(0, 0, 0, 0.1), 0 8px 8px rgba(0, 0, 0, 0.1), 0 16px 16px rgba(0, 0, 0, 0.1);
   position: relative;
   transition: box-shadow 0.5s ease;
}

.is-style-card--interactive:focus-within,
.is-style-card--interactive:hover {
   box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 2px rgba(0, 0, 0, 0.2), 0 4px 4px rgba(0, 0, 0, 0.2), 0 8px 8px rgba(0, 0, 0, 0.2), 0 16px 16px rgba(0, 0, 0, 0.2);
}

.is-style-card--interactive :where(.wp-block-group.wp-block-group-is-layout-constrained) {
   position: static;
}

/* Make whole card clickable */
.is-style-card--interactive:not(.has-child-selected) :where(.wp-block-heading) a:after {
   content: "";
   inset: 0;
   position: absolute;
   z-index: 10;
}

/* Animate the Cover block image */
.is-style-card--interactive :where(.wp-block-cover__image-background) {
   filter: saturate(100%) brightness(100%);
   transform: scale(1);
   transition: all 0.35s ease;
}

.is-style-card--interactive:not(.has-child-selected):focus-within :where(.wp-block-cover__image-background),
.is-style-card--interactive:not(.has-child-selected):hover :where(.wp-block-cover__image-background) {
   filter: saturate(200%) brightness(40%);
   transform: scale(1.15);
}

/* Animate label area */
.is-style-card--interactive :where(.is-vertical) .wp-block-group:first-of-type {
   opacity: 0;
   transform: scale(0.95) translateX(-1rem);
   transform-origin: center right;
   transition: all 0.25s ease-in-out;
   transition-delay: 0.2s;
}

.is-style-card--interactive:focus-within :where(.is-vertical) .wp-block-group:first-of-type,
.is-style-card--interactive:hover :where(.is-vertical) .wp-block-group:first-of-type {
   opacity: 1;
   transform: scale(1) translateX(0);
}

/* Animate content area */
.is-style-card--interactive:not(.has-child-selected) :where(.is-vertical) .wp-block-group:first-of-type + .wp-block-group p {
   max-height: 0;
   opacity: 0;
   overflow: hidden;
   transition: max-height 0.35s cubic-bezier(.19,1,.22,1), opacity 0.6s ease;
}

.is-style-card--interactive:focus-within :where(.is-vertical) .wp-block-group:first-of-type + .wp-block-group p,
.is-style-card--interactive:hover :where(.is-vertical) .wp-block-group:first-of-type + .wp-block-group p {
   max-height: 100%;
   opacity: 1;
}

.is-style-card--interactive :where(.is-vertical) {
   display: flex;
}

@media (prefers-reduced-motion: reduce) {
   .is-style-card--interactive *,
   .is-style-card--interactive *::after,
   .is-style-card--interactive *::before {
       opacity: 1 !important;
       transition: none !important;
       visibility: visible !important;
   }
}

Wrapping up

With a little planning and thought, you can progressively enhance your site’s interactivity and elevate the overall experience for your visitors.

You can find the complete plugin code in this GitHub repository. You’re welcome to fork it, customize it, and share how you use it in projects in the comments below. 

Props to @greenshady, @ironnysh, and @bph for the thoughtful feedback and review of this post.

Categories: , ,

11 responses to “Building a card layout with a “hover reveal” effect”

  1. Mateus Machado Luna Avatar

    Clickable cards gotta be the one block style I register every theme that I use… is there any reason why we cannot add link to group or cover blocks?

    1. Matt Knighton Avatar

      I agree. Having a clickable card is a very common pattern in web development. Making a card block do this by default along with the animation as a default block would be a very good idea.

      As the grid moves forward, making a card for use within the grid and having some responsive control would be highly effective.

      This would make bento boxes and other layouts possible with rules, without needing to resort to coding for every block layout.

    2. Damon Cook Avatar

      > is there any reason why we cannot add link to group or cover blocks?

      You can, but you must write custom code to extend the intended blocks. Offering this behavior to every kind of WordPress user poses a problem because users need to be aware of semantic HTML usage and the context for which the blocks are used when linking a Group or Cover block.

    3. Patrick Boehner Avatar

      One reason you may not want to do that ( add a wrapping link around ) is accessibility. If you added a link to wrap the entire group of content, then anytime anyone with a screen reader tabs into that group, they would hear all the text within that group read out to them as one long, continuous bit of text. That could be a lot ( headings, categories, author meta, descriptions ).

      If the block already has a link via a linked heading, then using a sudo element with some CSS to make the car clickable would be a better approach. Plus, you could still have it apply with a block style that targets linked headings within the block.

      1. Mateus Machado Luna Avatar
        Mateus Machado Luna

        That is a really interesting argument, I haven’t considered that.

        I feel we’re all kinda trying to mimic the cards pattern from Mobile UIs and things are a bit different on the web. How does it work with screen readers? Because I’ve also heard before that adding one link around everything could be better than putting on each child element because then people start putting redundant links in every child element (heading, image, etc) just to make it more interactive.

        It’s curious that the pseudo-element solution sounds hacky but ends up being better, even thought it is not very intuitive for whoever is building the block.

        1. Damon Cook Avatar

          The cards UI has been around for a long time. The best approach is always to consider semantics and accessibility and progressively enhance the UI based on context and needs. Here is an essential read from Heydon Pickering that may help guide some decisions: https://inclusive-components.design/cards/

          I hope it helps!

          1. Mateus Machado Luna Avatar

            Thanks, that is some great reading 🙂

  2. Hans-Gerd Gerhards Avatar

    Thank you for this great contribution.
    However, I would like to point out a small typo: themeslug instead of themslug
    add_action( ‘init’, ‘themeslug_register_block_styles’ );

    1. Damon Cook Avatar

      Thanks Hans-Gerg for pointing out this discrepancy. I’ve updated the code. Much appreciated!

  3. Trevor Robertson Avatar
    Trevor Robertson

    Love this idea to make the clickable cards with a bit of CSS. The only problem is when I use this pseudo-content trick it makes the heading text hard to edit. This doesn’t seem to happen for other, so it’s a bit odd. In my case, when I click on the heading in the editor the cursor is positioned at the end of the heading no matter where you click. As such the only way to edit the text is to delete it all. 🙁

    Any ideas why that is happening? If not, or if anyone else has that issue here a fix… Remove the positioning for the pseudo content in the editor using an `editor.css` file. Note, you need to have a theme the enqueues editor specific CSS. In that file put something like this:

    body .is-style-card–interactive :where(.wp-block-heading) a::after {
    position: relative;
    }

    1. Damon Cook Avatar

      > The only problem is when I use this pseudo-content trick it makes the heading text hard to edit.

      I’ve updated the GitHub repo and this article to address this. Here is the diff of CSS changes: https://github.com/wptrainingteam/hover-reveal-card/commit/e941d851a38f988afda60509924d2df9440338cd

      By utilizing the `:not(.has-child-selected)` we can assert editor-specific treatment to ensure a better editor experience, and you do not have to maintain two separate stylesheets. Thanks for pointing this out Trevor!

Leave a Reply

Your email address will not be published. Required fields are marked *