LESSON 6 โฑ๏ธ 11 min read

Block Variations & Transforms

Block Variations

Variations let you create presets of a single block with different default attributes:

// src/variations.js
import { __ } from '@wordpress/i18n';

export const variations = [
    {
        name: 'testimonial-simple',
        title: __( 'Simple Testimonial', 'theme-blocks' ),
        description: __( 'A minimal testimonial without rating.', 'theme-blocks' ),
        icon: 'format-quote',
        attributes: {
            showRating: false,
            showAvatar: false,
            style: 'minimal',
        },
        scope: [ 'inserter', 'block', 'transform' ],
        isDefault: true,
    },
    {
        name: 'testimonial-card',
        title: __( 'Testimonial Card', 'theme-blocks' ),
        description: __( 'Featured testimonial with avatar and rating.', 'theme-blocks' ),
        icon: 'id-alt',
        attributes: {
            showRating: true,
            showAvatar: true,
            style: 'card',
        },
        scope: [ 'inserter', 'block', 'transform' ],
    },
    {
        name: 'testimonial-featured',
        title: __( 'Featured Quote', 'theme-blocks' ),
        description: __( 'Large quote with decorative styling.', 'theme-blocks' ),
        icon: 'megaphone',
        attributes: {
            showRating: true,
            showAvatar: true,
            style: 'featured',
            align: 'wide',
        },
        scope: [ 'inserter' ],
    },
];

Register Variations

// src/index.js
import { registerBlockType, registerBlockVariation } from '@wordpress/blocks';
import metadata from './block.json';
import Edit from './edit';
import save from './save';
import { variations } from './variations';

registerBlockType( metadata.name, {
    edit: Edit,
    save,
    variations,
} );

Variation Scope

ScopeWhere It Appears
inserterBlock inserter panel
blockBlock settings panel
transformTransform menu

Core Block Variations

You can also create variations of core blocks:

// Create a custom "Hero" variation of the Cover block
registerBlockVariation( 'core/cover', {
    name: 'hero-section',
    title: __( 'Hero Section', 'theme-blocks' ),
    description: __( 'A full-width hero with overlay.', 'theme-blocks' ),
    attributes: {
        align: 'full',
        dimRatio: 50,
        minHeight: 600,
        contentPosition: 'center center',
    },
    innerBlocks: [
        [ 'core/heading', { level: 1, placeholder: 'Hero Title' } ],
        [ 'core/paragraph', { placeholder: 'Hero description...' } ],
        [ 'core/buttons', {}, [
            [ 'core/button', { text: 'Get Started' } ],
        ] ],
    ],
    scope: [ 'inserter' ],
} );

Block Transforms

Transforms let users convert between block types:

// src/index.js
import { createBlock } from '@wordpress/blocks';

registerBlockType( metadata.name, {
    edit: Edit,
    save,
    transforms: {
        from: [
            // Transform FROM core quote
            {
                type: 'block',
                blocks: [ 'core/quote' ],
                transform: ( { value, citation } ) => {
                    return createBlock( 'theme/testimonial', {
                        content: value,
                        author: citation,
                    } );
                },
            },
            // Transform FROM core paragraph
            {
                type: 'block',
                blocks: [ 'core/paragraph' ],
                transform: ( { content } ) => {
                    return createBlock( 'theme/testimonial', {
                        content,
                    } );
                },
            },
            // Transform FROM raw text (paste)
            {
                type: 'raw',
                selector: 'blockquote',
                schema: {
                    blockquote: {
                        children: {
                            p: { children: [] },
                            cite: { children: [] },
                        },
                    },
                },
                transform: ( node ) => {
                    const content = node.querySelector( 'p' )?.textContent || '';
                    const author = node.querySelector( 'cite' )?.textContent || '';
                    return createBlock( 'theme/testimonial', { content, author } );
                },
            },
        ],
        to: [
            // Transform TO core quote
            {
                type: 'block',
                blocks: [ 'core/quote' ],
                transform: ( { content, author } ) => {
                    return createBlock( 'core/quote', {
                        value: content,
                        citation: author,
                    } );
                },
            },
            // Transform TO core paragraph
            {
                type: 'block',
                blocks: [ 'core/paragraph' ],
                transform: ( { content } ) => {
                    return createBlock( 'core/paragraph', {
                        content,
                    } );
                },
            },
        ],
    },
} );

Multi-Block Transforms

Transform multiple blocks at once:

transforms: {
    from: [
        {
            type: 'block',
            blocks: [ 'core/paragraph' ],
            isMultiBlock: true,
            transform: ( paragraphs ) => {
                // Combine multiple paragraphs into one testimonial
                const content = paragraphs
                    .map( p => p.content )
                    .join( '<br>' );
                return createBlock( 'theme/testimonial', { content } );
            },
        },
    ],
}

Transform Priority

Control transform order with priority (lower = higher priority):

{
    type: 'block',
    blocks: [ 'core/quote' ],
    priority: 5, // Show earlier in transform menu
    transform: ( attributes ) => { /* ... */ },
}

isMatch Function

Conditionally allow transforms:

{
    type: 'block',
    blocks: [ 'core/paragraph' ],
    isMatch: ( { content } ) => {
        // Only transform if content looks like a quote
        return content?.includes( '"' );
    },
    transform: ( attributes ) => { /* ... */ },
}

Block Categories

Register a custom block category:

// In PHP
function theme_register_block_category( $categories ) {
    return array_merge(
        array(
            array(
                'slug'  => 'theme-blocks',
                'title' => __( 'Theme Blocks', 'theme-blocks' ),
                'icon'  => 'star-filled',
            ),
        ),
        $categories
    );
}
add_filter( 'block_categories_all', 'theme_register_block_category' );

Then use in block.json:

{
    "category": "theme-blocks"
}

Next Steps

In the next lesson, we'll add frontend interactivity with View Scripts and the Interactivity API.

๐ŸŽฏ Lesson Complete! You can now create block variations and seamless transforms between blocks.