How to Create Responsive Cover Block? (Updated 2023)

Want to have a different image for the Cover block on Desktop and Mobile? Visit us to learn how.

One basic feature that is lacking from Gutenberg’s Cover block is setting different images for desktop and mobile.

In this article, we will show two methods to do it: Simple and Advanced.

Simple Method: Custom Style

Create two custom styles: “Desktop Only” and “Mobile Only”.

Then create two Cover blocks, each having its respective style. This way you can set different image for the one appearing in Desktop and Mobile.

It’s not pretty, but it works!

Here’s the code to register custom styles and the CSS:

add_action('init', 'my_register_cover_styles');
add_action('wp_enqueue_scripts', 'my_enqueue_cover_assets', 99);

function my_register_cover_styles() {
  register_block_style('core/cover', [
    'name' => 'desktop-only',
    'label' => 'Desktop Only',
  ]);

  register_block_style('core/cover', [
    'name' => 'mobile-only',
    'label' => 'Mobile Only',
  ]);
}

function my_enqueue_cover_assets() {
  $dir = get_stylesheet_directory_uri() . '/css';
  wp_register_style('my-cover', $dir . '/my-cover.css', false);
}
@media (max-width: 768px) {
  .wp-block-cover.is-style-desktop-only {
    display: none;
  }
}

@media (min-width: 767px) {
  .wp-block-cover.is-style-mobile-only {
    display: none;
  }
}

Advanced Method: Custom Control

Use JavaScript to add new control into the Cover block as shown above. This looks more professional, although quite complex to implement.

Our goal is to wrap the image in a <picture>. This way, the browser will not load the desktop image on mobile and vice versa.

<!-- BEFORE -->

<div class="wp-block-cover">
  <img class="wp-block-cover__image-background" src="...">
  ...
</div>

<!-- AFTER -->

<div class="wp-block-cover">
  <picture>
    <source srcset="..." media="(max-width:767px)">
    <img class="wp-block-cover__image-background" src="...">
  </picture>
  ...
</div>

STEP 1: The JavaScript

Here we have my-cover.js. We won’t explain the syntax since it’s quite complex. If you want to learn from the basics, check out our Gutenberg Tutorial.

Then, enqueue the script. Change the directory according to your folder structure:

add_action('enqueue_block_editor_assets', 'enqueue_responsive_cover', 100);

function enqueue_responsive_cover() {
  $dir = get_stylesheet_directory_uri() . '/js';
  wp_enqueue_script('my-cover', $dir . '/my-cover.js', [ 'wp-blocks', 'wp-dom' ] , null, true);
}

Now check out the Cover block in Editor. Does the Mobile Upload section show up?

A common mistake is enqueuing the wrong directory. Open the browser’s console to see whether there is a 404 error for the JS file.

STEP 2: Modify Rendering Function

We will filter the rendering method and use Regex to wrap the image in <picture>.

(Update 2023) As of latest WordPress, if you set the Cover to “Fixed Background”, the <img> will be replaced with <div role="img">. That means we can no longer uses <picture>.

The code below has conditional to check that. If using <div>, it will add extra CSS variable instead of wrapping it in <picture>

add_filter('render_block_core/cover', 'my_responsive_cover_render', 10, 2);

function my_responsive_cover_render($content, $block) {
  // If has mobile image
  if (isset($block['attrs']['mobileImageURL'])) {
    $image = $block['attrs']['mobileImageURL'];

    preg_match('/<div role="img"/', $content, $is_fixed);

    // If fixed background, add CSS variable
    if ($is_fixed) {
      $content = preg_replace(
        '/(<div role="img".+style=".+)(">)/Ui',
        "$1;--mobileImageURL:url({$image});$2",
        $content
      );
    }
    // If not fixed, wrap in <picture>
    else {
      $content = preg_replace(
        '/<img class="wp-block-cover__image.+\/>/Ui',
        "<picture><source srcset='{$image}' media='(max-width:767px)'>$0</picture>",
        $content
      );
    }
  }

  return $content;
}

Then add this in your CSS to replace the background-image on mobile:

@media (max-width:768px) {
  .wp-block-cover__image-background[style*="--mobileImageURL"] {
    background-image: var(--mobileImageURL) !important;
  }
}

Done! Try resizing your browser’s window and you will see the image changing when it reaches the breakpoint.

Note that this setting only affect the frontend. It won’t be applied in the admin editor.

Conclusion

So which method to choose?

If it’s your personal website, using the Simple method is fine.

But for the client website, it will look more professional if you use the Advanced method. It’s easier to edit and less prone to mistakes too.

Let me know if you have any question in the comment below. Thank you for reading.

Default image
Henner Setyono
A web developer who mainly build custom theme for WordPress since 2013. Young enough to never had to deal with using Table as layout.
Leave a Reply

17 Comments

  1. This is just what I need. I tried the Simple Method but doesn't seem to work. The block only saves one style, either mobile or desktop. Whichever was set last, that's what is used on the page.

    I'm not a developer, but I don't see how it would work without changing or adding code to change how the block actually functions. Just adding style options doesn't appear to give the block the ability to save two different images.

    Any suggestions, or am I doing something wrong?

    Thanks

    • Hi tony,

      The idea of the simple method is having two Cover block, one having Desktop Only and the other having Mobile Only style

      • Ah yes, that makes perfect sense now. You may want to mention that in the article. Thank you.

        • Yes, thanks for the input, will revise my article

          • Hi, my appreciation for your work.

            I've been trying for months to implement something similar, but with one substantial difference, I don't want to hide the photo in mobile (or in desktop) with display:none, for the simple fact that the browser anyway loads it and this slows down the page for the user and for pagespeed (small problem in desktop but important in mobile because the hidden photo for desktop can weigh a lot). And so I'm asking if in your opinion it is possible to change the second mode you proposed so that wp_is_mobile is used to make the photo appear in mobile and its negation to make the photo appear in desktop.

            Thank you! Regards Harry

  2. Hi,

    To prevent the issue with loading JS, you better use get_stylesheet_directory_uri() insteed of get_template_directory_uri().

    Like this you ensure to load the child theme folder if a child theme is used.

  3. Very helpful, thanks! Can you publish this as a WordPress plugin? I would want this on all sites that use a cover block.

    • Hi strarsis,

      Currently I have no plan on releasing this as plugin as the publishing to wordpress repo takes a lot of time. But feel free to create your own. Thanks

  4. Hi, First of all thank you very much for this Responsive Cover Block; I managed to implement it very easily thanks to your easy to understand tutorial. Thank you so much. My question is related to Harry's question about the ability to dequeueing the 'desktop' version of the banner image from loading when the site is launched on mobile. Is this possible and how to do this? knowing that I am not a PHP dev despite some notions of integration.

    Hope you find time to answer that question. Have a nice day.

    • Hi Max,

      The <picture> tag only loads the image that is within the breakpoint. I just tested this by opening DevTool, going to Network tab, and refreshing the page. The desktop image isn't loaded in mobile screen.

      But during the testing I found another bug caused by the new Cover markup in WP 6.2. If you enable "Fixed Background", it changes the <img> tag to <div>, which mean <picture> won't work. I will find a fix and update the article accordingly.

  5. wish this worked but just doesnt do anything for me 🙁

    • Hi Adrian, the HTML markup of Gallery block has changed quite a bit since I wrote the guide. So this guide might not be valid anymore. I'll try finding time to update it

  6. Thanks for the code. It worked great except for not being able to clear the mobile image. I edited the code to add that feature.

    (function() {
      const el = wp.element.createElement;
      const { Fragment } = wp.element;
      const { InspectorControls, MediaUpload } = wp.blockEditor;
      const { createHigherOrderComponent } = wp.compose;
      const { addFilter } = wp.hooks;
      const { Button, PanelBody } = wp.components;
    
      addFilter('blocks.registerBlockType', 'extend-cover/attributes', addAttribute);
      addFilter('editor.BlockEdit', 'extend-cover/edit', createHigherOrderComponent(addControl));
    
      function addAttribute(settings, name) {
        // Abort if not Cover
        if (!['core/cover'].includes(name)) {
          return settings;
        }
    
        // add new attribute
        settings.attributes = Object.assign(settings.attributes, {
          mobileImageID: { type: 'number' },
          mobileImageURL: { type: 'string' },
        });
    
        return settings;
      }
    
      function addControl(BlockEdit) {
        return (props) =&gt; {
          if (!['core/cover'].includes(props.name)) {
            return el(BlockEdit, props);
          }
    
          const atts = props.attributes;
    
          return el(Fragment, {},
            el(BlockEdit, props),
            el(InspectorControls, {},
              el(PanelBody, {
                title: 'Mobile Cover',
                initialOpen: true,
              },
                el('div', {},
                  (atts.mobileImageURL &amp;&amp; atts.mobileImageID !== 0 &amp;&amp; // Only show image if ID is not 0
                    el('img', {
                      src: atts.mobileImageURL,
                    })
                  ),
                  el(MediaUpload, {
                    allowedTypes: 'image',
                    value: atts.mobileImageID,
                    onSelect: setMobileImage,
                    render: renderMobileImage,
                  })
                )
              )
            )
          );
    
          function setMobileImage(media) {
            props.setAttributes({
              mobileImageURL: media.url,
              mobileImageID: media.id,
            });
          }
    
          function renderMobileImage(obj) {
            return el(Button, {
              className: 'button',
              onClick: atts.mobileImageID === 0 ? obj.open : clearMobileImage, // Show "Upload image" when ID is 0, else show "Clear image"
            }, atts.mobileImageID === 0 ? 'Upload image' : 'Clear image');
          }
    
          // Function to clear the mobile image
          function clearMobileImage() {
            props.setAttributes({
              mobileImageURL: '',
              mobileImageID: 0,
            });
          }
        }
      }
    
    }());