Block Variations
Block variations create alternative versions of a block with pre-configured attributes and settings.
Basic Variation
Define in block.json:
{
"variations": [
{
"name": "blue",
"title": "Blue Card",
"attributes": {
"backgroundColor": "blue",
"textColor": "white"
}
}
]
}
Variation Properties
| Property | Type | Description |
|---|---|---|
name |
string | Unique identifier (required) |
title |
string | Display name (required) |
description |
string | Variation description |
attributes |
object | Pre-set attribute values |
innerBlocks |
array | Pre-set inner blocks |
icon |
string/object | Custom icon |
category |
string | Block category override |
keywords |
array | Additional search terms |
isDefault |
boolean | Is this the default variation? |
isActive |
function/array | Determine if variation is active |
scope |
array | Where variation appears |
example |
object | Preview content |
Defining Variations in JavaScript
For complex variations, define in JavaScript:
import { registerBlockVariation } from '@wordpress/blocks';
registerBlockVariation( 'core/embed', {
name: 'youtube',
title: 'YouTube',
icon: 'video-alt3',
keywords: [ 'video', 'youtube' ],
description: 'Embed a YouTube video.',
attributes: {
providerNameSlug: 'youtube',
responsive: true,
},
isActive: ( blockAttributes, variationAttributes ) =>
blockAttributes.providerNameSlug === variationAttributes.providerNameSlug,
} );
Variation Scope
Control where variations appear:
registerBlockVariation( 'my-plugin/card', {
name: 'featured-card',
title: 'Featured Card',
scope: [ 'inserter', 'block', 'transform' ],
attributes: {
isFeatured: true,
},
} );
Scope values:
inserter– Show in block inserterblock– Show in block toolbar’s transform optionstransform– Available via block transforms
isActive
Determines which variation a block instance matches.
Array Form (Simple)
Match by attribute values:
{
"variations": [
{
"name": "youtube",
"title": "YouTube",
"attributes": {
"providerNameSlug": "youtube"
},
"isActive": [ "providerNameSlug" ]
}
]
}
Function Form (Complex)
registerBlockVariation( 'core/embed', {
name: 'youtube',
title: 'YouTube',
attributes: {
providerNameSlug: 'youtube',
},
isActive: ( blockAttributes, variationAttributes ) => {
return blockAttributes.providerNameSlug === 'youtube';
},
} );
Multiple conditions:
isActive: ( blockAttributes ) => {
return (
blockAttributes.providerNameSlug === 'youtube' &&
blockAttributes.responsive === true
);
},
Inner Blocks in Variations
Pre-configure nested blocks:
registerBlockVariation( 'core/columns', {
name: 'two-columns-equal',
title: 'Two Columns (Equal)',
innerBlocks: [
[ 'core/column', { width: '50%' } ],
[ 'core/column', { width: '50%' } ],
],
scope: [ 'inserter' ],
} );
registerBlockVariation( 'core/columns', {
name: 'two-columns-sidebar',
title: 'Two Columns (Sidebar)',
innerBlocks: [
[ 'core/column', { width: '66.66%' } ],
[ 'core/column', { width: '33.33%' } ],
],
scope: [ 'inserter' ],
} );
With content in inner blocks:
registerBlockVariation( 'core/group', {
name: 'hero-section',
title: 'Hero Section',
attributes: {
align: 'full',
style: {
spacing: {
padding: {
top: 'var:preset|spacing|80',
bottom: 'var:preset|spacing|80',
},
},
},
},
innerBlocks: [
[ 'core/heading', {
level: 1,
placeholder: 'Hero Title',
textAlign: 'center',
} ],
[ 'core/paragraph', {
placeholder: 'Hero subtitle or description',
align: 'center',
} ],
[ 'core/buttons', { layout: { justifyContent: 'center' } }, [
[ 'core/button', { placeholder: 'Call to Action' } ],
] ],
],
scope: [ 'inserter' ],
} );
Variation Icons
Custom icons per variation:
registerBlockVariation( 'my-plugin/card', {
name: 'horizontal-card',
title: 'Horizontal Card',
icon: 'align-left',
attributes: {
layout: 'horizontal',
},
} );
// Or with custom SVG
registerBlockVariation( 'my-plugin/card', {
name: 'vertical-card',
title: 'Vertical Card',
icon: (
<svg viewBox="0 0 24 24">
<path d="M4 4h16v16H4z" />
</svg>
),
attributes: {
layout: 'vertical',
},
} );
Setting Default Variation
Mark one variation as default:
registerBlockVariation( 'my-plugin/container', {
name: 'standard',
title: 'Standard Container',
isDefault: true,
attributes: {
padding: 'medium',
},
} );
Unregistering Variations
Remove existing variations:
import { unregisterBlockVariation } from '@wordpress/blocks';
// Remove the YouTube embed variation
wp.domReady( () => {
unregisterBlockVariation( 'core/embed', 'youtube' );
} );
Overriding Core Variations
Modify existing variations:
import { getBlockVariations, unregisterBlockVariation, registerBlockVariation } from '@wordpress/blocks';
wp.domReady( () => {
// Get existing variation
const variations = getBlockVariations( 'core/embed' );
const youtube = variations.find( v => v.name === 'youtube' );
if ( youtube ) {
// Unregister original
unregisterBlockVariation( 'core/embed', 'youtube' );
// Register modified version
registerBlockVariation( 'core/embed', {
...youtube,
title: 'Custom YouTube',
keywords: [ ...youtube.keywords, 'custom' ],
} );
}
} );
Variation Example Content
Provide preview in inserter:
registerBlockVariation( 'my-plugin/testimonial', {
name: 'customer-review',
title: 'Customer Review',
attributes: {
style: 'card',
},
example: {
attributes: {
quote: 'This product exceeded my expectations!',
author: 'Jane Smith',
rating: 5,
},
},
} );
Complete Example: Social Links Variations
const socialVariations = [
{
name: 'facebook',
title: 'Facebook',
icon: 'facebook',
attributes: {
service: 'facebook',
url: 'https://facebook.com/',
},
},
{
name: 'twitter',
title: 'Twitter',
icon: 'twitter',
attributes: {
service: 'twitter',
url: 'https://twitter.com/',
},
},
{
name: 'instagram',
title: 'Instagram',
attributes: {
service: 'instagram',
url: 'https://instagram.com/',
},
icon: (
<svg viewBox="0 0 24 24">
<path d="M12 2c2.717 0 3.056.01 4.122.06..." />
</svg>
),
},
];
socialVariations.forEach( ( variation ) => {
registerBlockVariation( 'core/social-link', {
...variation,
isActive: ( blockAttributes ) =>
blockAttributes.service === variation.attributes.service,
} );
} );
Query Loop Variations
Create custom query patterns:
registerBlockVariation( 'core/query', {
name: 'recent-posts-grid',
title: 'Recent Posts Grid',
description: 'Display recent posts in a grid layout',
attributes: {
query: {
perPage: 6,
postType: 'post',
orderBy: 'date',
order: 'desc',
},
displayLayout: {
type: 'flex',
columns: 3,
},
},
innerBlocks: [
[ 'core/post-template', {}, [
[ 'core/post-featured-image' ],
[ 'core/post-title' ],
[ 'core/post-excerpt' ],
] ],
[ 'core/query-pagination' ],
],
scope: [ 'inserter' ],
isActive: ( { query } ) => query.perPage === 6 && query.orderBy === 'date',
} );
PHP Registration
Register variations from PHP:
add_action( 'init', function() {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/group' );
if ( $block_type ) {
$block_type->variations = array_merge(
$block_type->variations ?? [],
[
[
'name' => 'full-width-section',
'title' => __( 'Full Width Section', 'my-plugin' ),
'attributes' => [
'align' => 'full',
'layout' => [
'type' => 'constrained',
],
],
'scope' => [ 'inserter' ],
],
]
);
}
} );
Or use the filter:
add_filter( 'get_block_type_variations', function( $variations, $block_type ) {
if ( $block_type->name === 'core/columns' ) {
$variations[] = [
'name' => 'three-equal',
'title' => __( 'Three Equal Columns', 'my-plugin' ),
'innerBlocks' => [
[ 'core/column', [ 'width' => '33.33%' ] ],
[ 'core/column', [ 'width' => '33.33%' ] ],
[ 'core/column', [ 'width' => '33.33%' ] ],
],
'scope' => [ 'inserter' ],
];
}
return $variations;
}, 10, 2 );
Best Practices
-
Use meaningful names: Variation names should clearly indicate their purpose.
-
Set isActive correctly: Ensure variations are properly identified when editing.
-
Provide good descriptions: Help users understand what each variation does.
-
Use appropriate scope: Don’t clutter the inserter with every variation.
-
Include example content: Makes variations easier to understand in the inserter.
-
Group related variations: Keep variations for the same block together.
-
Consider transforms: Enable variations in transform scope when switching makes sense.