WordPress.org

WordPress Developer Blog

An introduction to block variations

Block variations are one of the most powerful ways to extend WordPress and curate the editing experience for your users. In this article, you will learn how to effectively create block variations and discover ways to incorporate them into your workflows. Let’s get started.

The Block Variations API

The Block Variations API allows you to create additional versions of existing blocks that differ by a set of initial attributes or inner blocks.

A variation is defined by an object that contains a set of properties. I will cover many of the most important properties in this article, and you can review the rest in the Block Variations API documentation. 

  • name (type string) – A unique and machine-readable name.
  • title (optional, type string) – A human-readable variation title.
  • icon (optional, type string | Object) – An icon for the variation in the Editor UI.
  • attributes (optional, type Object) – Values that override the original block’s default attributes.
  • innerBlocks (optional, type Array[]) – The initial configuration of nested blocks.
  • isDefault (optional, type boolean) – Defaults to false. Indicates whether the current variation is the default one.
  • isActive (optional, type Function | string[]) – A function or an array of block attributes that are used to determine if the variation is active when the block is selected. The function accepts blockAttributes and variationAttributes.

Once defined, variations can be created using the function registerBlockVariation() or unregistered using unregisterBlockVariation(). The examples below demonstrate how to use both.

Variations can also be declared during the registration of a block. Refer to the Block Registration API for more details. This approach is useful if you are building a custom block and want to provide additional variations.

Getting set up

Block variations are registered in JavaScript, so you first need to create a JavaScript file. 

I am going to assume you want to add block variations to a theme, but this approach can be easily adapted to a plugin. The only real difference is where the files and functions are located. For now, let’s create variations.js and put the file in our theme’s assets/js folder.

Next, you need to enqueue variations.js using the enqueue_block_editor_assets hook and the standard wp_equeue_script() function. This code should generally be placed in your theme’s functions.php file unless you have a more advanced setup.

function example_enqueue_block_variations() {
	wp_enqueue_script(
		'example-enqueue-block-variations',
		get_template_directory_uri() . '/assets/js/variations.js',
		array( 'wp-blocks', 'wp-dom-ready' ),
		wp_get_theme()->get( 'Version' ),
		false
	);
}
add_action( 'enqueue_block_editor_assets', 'example_enqueue_block_variations' );

Note that wp-blocks and wp-dom-ready are listed as dependencies. These will be used to register and unregister the block variations in the following steps, but the setup is complete for now.  

The WordPress Developer Blog typically uses ESNext syntax for JavaScript examples. However, since it’s common to add block variations to themes that do not include a build process, the examples here will use plain JavaScript. If your project already has a build process, feel free to use ESNext instead. A great resource for this approach can be found in Beyond block styles, part 1: using the WordPress scripts package with themes.

Creating a block variation

With the variation.js file enqueued, you can now register block variations using the registerBlockVariation() function. This function comes from the wp-blocks dependency that I added to the enqueue function earlier and can be called by prefixing the function with wp.block., or like this:

const { registerBlockVariation } = wp.blocks;

registerBlockVariation( ... );

Use whichever method you prefer—I will use wp.blocks.registerBlockVariation() throughout this article. 

The registerBlockVariation() function accepts the name of the original block and an object that defines the variation you want to create. Let’s look at an example.

wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'media-text-custom',
		title: 'Media & Text Custom',
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary'
		},
	}
);

The code above registers a block variation for the Media & Text block (core/media-text). I have given it a unique name and a title, and have set a few attribute values. When this variation is inserted into the Editor, it will be a Media & Text block aligned wide, with a background color set to tertiary

You can technically create a block variation without a unique name, although I recommend against this practice. A unique name allows the Editor to differentiate between your variation and others that may exist.

 The variation will appear like this when using the Twenty Twenty-Three theme:

You may have noticed that both the Media & Text block and the “Media & Text Custom” variation appear in the Inserter. This is the right outcome, but you can add the property isDefault: true to replace the original block with your variation. This method comes in handy when you want to modify the initial state of Core blocks, or third-party blocks. 

Let’s update the example so that the variation becomes the default. With this change, Media & Text blocks are always aligned wide and have a background color when inserted.

wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'media-text-default',
		title: 'Media & Text',
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary'
		},
		isDefault: true
	}
);

Since the variation is now the default, I have changed the title back to “Media & Text” and the Inserter will now only display a single Media & Text block. From a user perspective, they are just using the default block.

There are additional considerations to be aware of when you use isDefault, especially if the block already has another variation set to isDefault. Refer to the documentation for more information.

Unregistering block variations

Lots of Core WordPress blocks are actually variations. For instance, the Group block has four variations: Group, Row, Stack, and Grid (experimental). The Embed and Social Icon blocks are also great examples. 

Sometimes, you might not need certain block variations on a site. Removing these choices can simplify the Inserter and improve the user experience. To achieve this, use the unregisterBlockVariation function. Just provide the original block name and the variation name you want to remove.

The following code unregisters the Stack block.

wp.domReady( () => {
	wp.blocks.unregisterBlockVariation( 'core/group', 'group-stack' );
});

Using wp.domReady is required when unregistering Core blocks. Without it, the function fires too early, and the variation is never removed. While wp.domReady might not be required for unregistering some third-party blocks, it’s a good idea to use it consistently. Registering block variations does not require wp.domReady.

Creating a variation with inner blocks

Now let’s return to the Media & Text example above. While this block variation sets the align and backgroundColor attributes, what if you want to add default content to the text portion of the block?

We can do this using the innerBlocks property, which lets you specify an array of blocks to include within the variation upon insertion. You can also set the attributes of these inner blocks. 

In the example below, I have added a Header and a Paragraph block to the original example. Each of these also has a placeholder. This will help users create content more consistently.

wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'media-text-default',
		title: 'Media & Text',
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary'
		},
		innerBlocks: [
			[
				'core/heading',
				{
					level: 3,
					placeholder: 'Heading'
				} 
			],
			[
				'core/paragraph',
				{
					placeholder: 'Enter content here...'
				} 
			],
		],
		isDefault: true,
	}
);

The block variation will now look like this when inserted:

The innerBlocks property will only work for blocks that contain other blocks. However, this functionality can be incredibly powerful when combined with block and template locking. While locking is beyond the scope of this article, you can learn more in the Curating the Editor Experience guide.

Using the isActive property

Next up is the isActive property, which helps the Editor determine if the variation is active.

To illustrate how this property works, let’s create a new block variation called Text & Media. It will be similar to the previous example, but when inserted, the text will be on the left with the image on the right. This behavior is defined using the attribute mediaPosition: 'right'.

wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'text-media',
		title: 'Text & Media',
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary',
			mediaPosition: 'right'
		}
	}
);

The variation will appear in the Inserter alongside the default Media & Text block as expected. However, after inserting the variation, if the user selects the block again, it will be labeled “Media & Text” not “Text & Media”. 

This mislabeling occurs because the Editor cannot differentiate between the variation and the original Media & Text block. The problem is exacerbated if the variation has custom description and icon properties and can cause confusion for users.

The isActive property provides a solution and accepts either a function or an array of attribute strings. I will use the array notation here, but you can refer to the Block Variations API documentation for function examples. 

Text & Media variation is largely defined by the mediaPosition attribute, so let’s set isActive: [ ‘mediaPosition’ ].

wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'text-media',
		title: 'Text & Media',
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary',
			mediaPosition: 'right'
		},
		isActive: [ 'mediaPosition' ]
	}
);

With this property applied, the Editor will compare the attributes of a selected Media & Text block to any registered variations. If it finds a variation with a mediaPosition attribute that matches the selected block, mediaPosition: 'right' in this case, it will understand that the user has selected the Text & Media variation. The descriptive information of the block will be updated accordingly.

If you list multiple attributes in the array, each one will be checked. The variation will only be considered “active” if they all match.

Adding custom icons

To keep things simple, the previous examples didn’t have custom icons. However, giving block variation its own icon is a great way to improve its discoverability in the Inserter. You can assign an icon using the icon property, which takes a string or an object.
When using a string, it must be associated with a Dashicon. No other strings will work. For example, the Dashicon slug for the WordPress icon is dashicons-wordpress. Remove the dashicons- prefix and set icon: ‘wordpress’ to use this icon. The result will look like this:

In most situations, you will probably want to add a custom SVG icon instead of a Dashicon. This can be accomplished by passing an object to the icon property. 

Looking back at the Text & Media example created earlier, the icon does not accurately represent the default state of the block variation. The media should be on the right, not the left.

The icon on the right of the image was created using the original Media & Text icon and a bit of manipulation in Figma. Note that custom icons should generally be 24 pixels square. Below is the SVG code.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <path d="M21 17.5L21 6L13 6L13 17.5L21 17.5ZM10 14.5L3 14.5L3 16L10 16L10 14.5ZM3 11L10 11L10 12.5L3 12.5L3 11ZM10 7.5L3 7.5L3 9L10 9L10 7.5Z"></path>
</svg>

You can browse the full WordPress icon library in the Components Storybook, and then extract the SVG code using the browser inspector. 

The next step is to add this SVG to the icon property as an object. This notation can be tricky when registering block variations using plain JavaScript instead of ESNext.  You can explore the source code for Core blocks on GitHub for plenty of ESNext syntax examples, which will not be covered in this context.

The simplest solution to add the custom SVG using plain JavaScript is to leverage WordPress’s built-in components and functions—similar to what you’re already doing with registerBlockVariation, unregisterBlockVariation, and domReady.

In the enqueue function located in your functions.php file, add the dependencies for wp-element and wp-primitives. You can then use createElement, SVG, and Path to create the icon object.

const textMediaIcon = wp.element.createElement(
	wp.primitives.SVG,
	{ xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24" },
	wp.element.createElement(
		wp.primitives.Path,
		{
			d: "M21 17.5L21 6L13 6L13 17.5L21 17.5ZM10 14.5L3 14.5L3 16L10 16L10 14.5ZM3 11L10 11L10 12.5L3 12.5L3 11ZM10 7.5L3 7.5L3 9L10 9L10 7.5Z",
		}
	)
);

The code might look a bit complicated, but compare it to the raw SVG markup above, and you will be able to spot the similarities. 

Finally, set icon: textMediaIcon, and the result will look like this:

Putting it all together

Combining all of the examples in this article, your final variations.js file will:

  • Add a Media & Text block variation with inner blocks and a colored background that is set to isDefault.
  • Add a Text & Media block variation with a custom icon that demonstrates isActive
  • Unregister the Core Stack variation of the Group block.

Here is the complete file:

// Register a new default Media & Text block variation.
wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'media-text-default',
		title: 'Media & Text',
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary'
		},
		innerBlocks: [
			[
				'core/heading',
				{
					level: 3,
					placeholder: 'Heading'
				} 
			],
			[
				'core/paragraph',
				{
					placeholder: 'Enter content here...'
				} 
			],
		],
		isDefault: true,
	}
);

// Define the icon for the Text & Media block variation.
const textMediaIcon = wp.element.createElement(
	wp.primitives.SVG,
	{ xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24" },
	wp.element.createElement(
		wp.primitives.Path,
		{
			d: "M21 17.5L21 6L13 6L13 17.5L21 17.5ZM10 14.5L3 14.5L3 16L10 16L10 14.5ZM3 11L10 11L10 12.5L3 12.5L3 11ZM10 7.5L3 7.5L3 9L10 9L10 7.5Z",
		}
	)
);

// Register the Text & Media block variation.
wp.blocks.registerBlockVariation(
	'core/media-text',
	{
		name: 'text-media',
		title: 'Text & Media',
		icon: textMediaIcon,
		attributes: {
			align: 'wide',
			backgroundColor: 'tertiary',
			mediaPosition: 'right'
		},
		isActive: [ 'mediaPosition' ]
	}
);

// Unregister the Stack block variation.
wp.domReady( () => {
	unregisterBlockVariation( 'core/group', 'group-stack' );
});

Finally, this variations.js should be enqueued in your theme’s functions.php file with the necessary dependencies.

function example_enqueue_block_variations() {
	wp_enqueue_script(
		'example-enqueue-block-variations',
		get_template_directory_uri() . '/assets/js/variations.js',
		array( 'wp-blocks', 'wp-dom-ready', 'wp-element', 'wp-primitives' ),
		wp_get_theme()->get( 'Version' ),
		false
	);
}
add_action( 'enqueue_block_editor_assets', 'example_enqueue_block_variations' );

This article only scratched the surface of what can be accomplished with block variations, but you now have the basics, so it’s time to start experimenting yourself. Consider how to use variations in your next project and share your implementations with the community. 

If you want to see a follow-up to this article with more advanced examples, let me know in the comments.

19 responses to “An introduction to block variations”

  1. Juan Pablo Avatar

    Hi I loved this tutorial, thanks for it.
    Will be great if the icon and variation name can be show in view list too, I think can this help to identify variations in a better way when user is editing.

    1. Nick Diego Avatar

      Thanks for the kind words Juan. So long as you use the `isActive` property, the icon and variation name should display in the list view.

  2. Dominique Pijnenburg Avatar

    I added a variation for the `core/button` but noticed that it’s not possible to use `isDefault` there (yet?). That’s very unfortunate!

    1. Nick Diego Avatar

      Yeah, there are a few Core blocks that do not interact very well with variations, the Buttons block being one of them. There is an open issue to fix this.

      1. Juan Pablo Avatar

        I try did with block group, doesn’t work

      2. Dominique Pijnenburg Avatar

        This is actually my own issue, haha! 😜
        I hope a fix will be realised soon.

  3. Wajeeha Arooj Avatar
    Wajeeha Arooj

    I attempted to do this, and while I’ve encountered some obstacles, I am actively working to find a solution and will not give up until I succeed.

  4. dilrubaummer Avatar

    I have created a new block variation for the WooCommerce checkout block, and while it functions correctly on the frontend checkout page, it fails to work in the Gutenberg editor after a page reload.

    import {registerBlockVariation} from ‘wordpress/blocks’;

    registerBlockVariation(
    ‘woocommerce/checkout’,
    {
    name: ‘themehigh-checkout-form-block’,
    title: ‘ThemeHigh Checkout Form Block’,
    category: “themehigh-checkout-block”,
    attributes: {
    align: ‘wide’,
    },
    innerBlocks: [

    [
    ‘core/paragraph’,
    {
    placeholder: ‘Enter content here…’
    }
    ],

    ],
    isDefault: true
    }
    );

    1. Nick Diego Avatar

      I believe this is because that block contains required inner blocks to function. If they do not exist, the block content refreshes. This is a best guess without diving into the code of the Checkout block.

  5. Aileen Avatar
    Aileen

    Very helpful, yes, please do a follow-up article with more examples!

    Also, it is possible to hide settings that you’d rather not be changed?

  6. Juan Manuel Solis Avatar
    Juan Manuel Solis

    i want to set up a colors for Background Color of a variation of Button block but it doesn’t show up

  7. David F Carr Avatar

    I couldn’t find anything in the documentation that clearly spells out how to use block variations in combination with block transforms. In particular, I wanted to transform a single block into a variation including inner blocks. Here is my solution https://davidfcarr.com/transforming-a-single-wordpress-block-into-a-block-variation-with-innerblocks/

    1. Nick Diego Avatar

      Thanks for sharing David, I have not worked much with transforms.

  8. Sascha Paukner Avatar
    Sascha Paukner

    Thank you for this great tutorial!
    Any idea how I can change the block preview that is showing up, when I hover over the block in the inserter menu? I would really like to change this.

  9. Sascha Paukner Avatar
    Sascha Paukner

    Hi Nick,
    I actually found the solution to the problem above. But I have another question:
    Is it be possible to override the supports settings in a block variation? It seems not.
    Would it make sense to add support for overriding this? What are your thoughts?

    1. Nick Diego Avatar

      Not that I know of, but supports for a block type can be modified in a number of different ways. The easiest is with the block_type_metadata filter. If you click that link, there is an example of how to disable specific block supports for a block. The tricky bit is that I am not sure you can apply this at the block variation level. I’ll need to do some more research on this.

  10. Sascha Paukner Avatar
    Sascha Paukner

    Oh, and another thing, just in case people stumble over this:
    When you create a variation from core/group the isActive check does not work.
    See: https://github.com/WordPress/gutenberg/issues/41303

    Are there any plans to fix this?

    1. Nick Diego Avatar

      I would add your use case to that issue if you have time. The more comments on the issue, the more likely it is to get attention.

  11. Dave L Avatar

    So one issue I see around this is WordPress only provides a capability for the BASE block, but either there needs to be the ability to enable access to the base block but disable access to the variations separately. So the way I see it WordPress Core should do one of three things:

    1. There is a default capability of core//variations that you can enable & disable all variations of that block as needed
    2. registerBlockVariation creates a named capability by default following the structure of core// (i.e. core/media-text/media-text-default from your example)
    3. registerBlockVariation allows the block variant creator to create the specific capabilities as part of the initial JSON definition.

Leave a Reply

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