WordPress Block Editor: Creating Custom Block Toolbars and Inspector Controls

Beyond basic block attributes, the block editor allows you to add custom controls to the block toolbar (contextual, inline controls that appear above the selected block) and the Inspector panel (the sidebar controls under “Block” tab). Using BlockControls, InspectorControls, ToolbarGroup, and PanelBody from @wordpress/block-editor you can build rich editing UIs that feel native to Gutenberg.

Problem: The default Gutenberg block toolbar only shows block-type actions — there is no mechanism for a plugin to add contextual toolbar buttons that read block attributes and trigger attribute updates or side effects specific to the selected block.

Solution: Register a block toolbar extension using registerBlockType's edit function — add BlockControls from @wordpress/block-editor inside the edit component. Use ToolbarGroup and ToolbarButton to add custom controls. Read attributes with props.attributes and update them with props.setAttributes(). The toolbar renders only when the block is selected.


The JavaScript below adds a toolbar alignment group with custom icons, an Inspector panel with a range control and colour picker, and a toggle that switches between two block display modes — all wired to block attributes.


const { registerBlockType }                     = wp.blocks;
const { BlockControls, InspectorControls,
        ColorPalette, useBlockProps }            = wp.blockEditor;
const { ToolbarGroup, ToolbarButton,
        PanelBody, RangeControl,
        ToggleControl, SelectControl }           = wp.components;
const { __ }                                    = wp.i18n;
const { useState }                              = wp.element;

registerBlockType( 'my-plugin/feature-card', {
    apiVersion: 3,
    title:      __( 'Feature Card', 'my-plugin' ),
    category:   'design',
    attributes: {
        columns:         { type: 'integer', default: 3 },
        backgroundColor: { type: 'string',  default: '' },
        displayMode:     { type: 'string',  default: 'grid' },
        showBorder:      { type: 'boolean', default: false },
    },

    edit( { attributes, setAttributes } ) {
        const { columns, backgroundColor, displayMode, showBorder } = attributes;
        const blockProps = useBlockProps( {
            style: { backgroundColor: backgroundColor || undefined },
            className: `display-${displayMode}`,
        } );

        return wp.element.createElement( wp.element.Fragment, null,

            // ── Toolbar controls ────────────────────────────────────────
            wp.element.createElement( BlockControls, null,
                wp.element.createElement( ToolbarGroup, null,
                    wp.element.createElement( ToolbarButton, {
                        icon:      'grid-view',
                        label:     __( 'Grid layout', 'my-plugin' ),
                        isActive:  displayMode === 'grid',
                        onClick:   () => setAttributes( { displayMode: 'grid' } ),
                    } ),
                    wp.element.createElement( ToolbarButton, {
                        icon:      'list-view',
                        label:     __( 'List layout', 'my-plugin' ),
                        isActive:  displayMode === 'list',
                        onClick:   () => setAttributes( { displayMode: 'list' } ),
                    } )
                )
            ),

            // ── Inspector (sidebar) controls ────────────────────────────
            wp.element.createElement( InspectorControls, null,
                wp.element.createElement( PanelBody,
                    { title: __( 'Layout', 'my-plugin' ), initialOpen: true },
                    wp.element.createElement( RangeControl, {
                        label:    __( 'Columns', 'my-plugin' ),
                        value:    columns,
                        onChange: val => setAttributes( { columns: val } ),
                        min: 1, max: 6,
                    } ),
                    wp.element.createElement( ToggleControl, {
                        label:    __( 'Show border', 'my-plugin' ),
                        checked:  showBorder,
                        onChange: val => setAttributes( { showBorder: val } ),
                    } )
                ),
                wp.element.createElement( PanelBody,
                    { title: __( 'Colour', 'my-plugin' ), initialOpen: false },
                    wp.element.createElement( 'p', null, __( 'Background', 'my-plugin' ) ),
                    wp.element.createElement( ColorPalette, {
                        value:    backgroundColor,
                        onChange: val => setAttributes( { backgroundColor: val || '' } ),
                    } )
                )
            ),

            // ── Block preview in editor ─────────────────────────────────
            wp.element.createElement( 'div', { ...blockProps },
                wp.element.createElement( 'p', null, `Feature Card — ${columns} cols, ${displayMode}` )
            )
        );
    },

    save: () => null,  // dynamic block — rendered by PHP
} );


NOTE: InspectorControls renders into the block sidebar only when the block is selected; wrap controls that affect the global layout (like theme-wide colour palettes) in InspectorControls.Slot with a group="styles" prop to ensure they appear in the Styles tab rather than the default Block tab.