Adding Image Controls to a Gutenberg Block

A custom block I worked on recently needed the ability to display an image. To do that, I needed a control to add or upload an image from the Media Library.

Add the attributes

The block needed attributes to store the image ID, URL, and alt text. The alt text is read from the alt attribute on the block’s img tag.

"imageUrl": {
  "type": "string"
},
"imageId": {
  "type": "number"
},
"imageAlt": {
  "type": "string",
  "source": "attribute",
  "selector": "img",
  "attribute": "alt",
  "default": ""
}

Handle Image Attribute Changes

We’ll define a function to update the attributes whenever a new media object was passed in. The media object includes the image ID, URL, and alt text.

const setImageAttributes = (media) => {
    if (!media || !media.url) {
        setAttributes({
            imageUrl: null,
            imageId: null,
            imageAlt: null,
        });
        return;
    }
    setAttributes({
        imageUrl: media.url,
        imageId: media.id,
        imageAlt: media?.alt,
    });
};

Add the MediaPlaceholder Component

The MediaPlaceholder component renders the editing interface to replace the media for a block. Here’s it’s configured to allow only images (but images of any filetype) and to allow only one image to be selected.

<MediaPlaceholder
    accept="image/*"
    allowedTypes={['image']}
    onSelect={setImageAttributes}
    multiple={false}
    handleUpload={true}
/>

Add the MediaReplaceFlow component

The MediaReplaceFlow component, added to the “Block Tools”, gives the user the option to click “Add Image”/“Replace Image” in the block toolbar in addition to using the MediaPlaceholder interface.

<BlockControls>
    <MediaReplaceFlow
        mediaId={imageId}
        mediaUrl={imageUrl}
        allowedTypes={['image']}
        accept="image/*"
        onSelect={setImageAttributes}
        name={!imageUrl ? __('Add Image') : __('Replace Image')}
    />
</BlockControls>

Display the image

Add an img tag if the URL exists

Once the image has been uploaded and the imageUrl attribute property is set, the image can be displayed on the block using an img tag.

{imageUrl && <img src={imageUrl} alt={imageAlt} />}

Position the MediaPlaceholder

The MediaPlaceholder interface is a regular div and will display below the img (or wherever you put it in the block). To position it on top of the image, similar to the Cover block, we need to move it with a bit of (S)CSS. (.card__media here is a custom class that wraps around both the img and the MediaPlaceholder.)

.card__media {
	min-height: 200px;
	position: relative;
}

.card__media img + .block-editor-media-placeholder {
	background-color: rgba(255, 255, 255, 0.7);
	box-shadow: none;
	height: 100%;
	left: 0;
	position: absolute;
	top: 0;
	width: 100%;
}

Add a Spinner

One last touch is to display the <Spinner /> component while the image is being uploaded. While that’s happening, the imageUrl will be set to a temporary Blob URL and the ID won’t exist.

import { isBlobURL } from '@wordpress/blob';
const isTemporaryMedia = (id, url) => !id && isBlobURL(url);

Then within the component, we can check if the image is uploading by checking if it’s temporary, and displaying the spinner if so.

const isUploadingMedia = isTemporaryMedia(imageId, imageUrl);
...
{isUploadingMedia && <Spinner />}
References