Top 5 React-Based Gutenberg Block Plugins for Modern Custom Themes for Independent Web Developers and Indie Hackers
Leveraging React for Advanced Gutenberg Block Development
For independent web developers and indie hackers building modern, custom WordPress themes, especially those targeting e-commerce, the Gutenberg block editor presents a powerful opportunity. Moving beyond basic text and image blocks, a truly dynamic and interactive user experience often necessitates custom blocks. While PHP remains the backbone of WordPress, integrating React for the block editor’s interface offers a significantly more robust and performant development environment. This approach allows for complex UIs, state management, and reusable components, mirroring modern frontend development practices. Here, we explore five key React-based Gutenberg block plugins that empower developers to create sophisticated, custom blocks for their themes.
1. Block Lab: A Developer-Centric Approach
Block Lab is not strictly a React plugin itself, but it’s a crucial enabler for React-based block development. It provides a PHP-based API to register custom blocks, and importantly, it allows you to define the block’s edit and save functions using JavaScript, which can be written in React. This separation of concerns is excellent for teams or solo developers who want to leverage React’s power without being bogged down by the intricacies of the full WordPress plugin development lifecycle for every single block.
The core idea is to define your block’s attributes and fields in PHP, and then use a JavaScript file (which you can build with React) to render the editor interface. Block Lab handles the registration and data serialization.
Example: Registering a Block with Block Lab and Using React for the Editor
First, you’d register your block in your theme’s `functions.php` or a custom plugin:
<?php
/**
* Register a custom block using Block Lab.
*/
function my_theme_register_react_block() {
if ( function_exists( 'block_lab_register_block' ) ) {
block_lab_register_block( array(
'block_name' => 'my-theme/react-example',
'title' => 'React Example Block',
'category' => 'theme',
'icon' => 'star-filled',
'attributes' => array(
'content' => array(
'type' => 'string',
'default' => '',
),
'alignment' => array(
'type' => 'string',
'default' => 'none',
),
),
'editor_script' => 'my-theme-react-editor-script', // Handle registered below
'editor_style' => 'my-theme-react-editor-style', // Handle registered below
) );
}
}
add_action( 'init', 'my_theme_register_react_block' );
/**
* Enqueue editor scripts and styles.
*/
function my_theme_enqueue_editor_assets() {
// Assuming you have a build process that outputs these files.
$asset_file = include( get_template_directory() . '/build/index.asset.php' );
wp_enqueue_script(
'my-theme-react-editor-script',
get_template_directory_uri() . '/build/index.js',
$asset_file['dependencies'],
$asset_file['version']
);
wp_enqueue_style(
'my-theme-react-editor-style',
get_template_directory_uri() . '/build/index.css',
array( 'wp-edit-blocks' ),
$asset_file['version']
);
}
add_action( 'enqueue_block_editor_assets', 'my_theme_enqueue_editor_assets' );
?>
Then, in your React application (e.g., `src/index.js` if using a build tool like Webpack or Vite):
import { registerBlockType } from '@wordpress/blocks';
import { RichText, InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
// Import your React components
import './style.scss'; // For editor and frontend styles
registerBlockType( 'my-theme/react-example', {
edit: ( { attributes, setAttributes } ) => {
const blockProps = useBlockProps( {
className: `my-theme-react-example align-${ attributes.alignment }`,
} );
const onChangeContent = ( newContent ) => {
setAttributes( { content: newContent } );
};
const onChangeAlignment = ( newAlignment ) => {
setAttributes( { alignment: newAlignment } );
};
const alignmentOptions = [
{ label: __( 'None', 'my-theme' ), value: 'none' },
{ label: __( 'Left', 'my-theme' ), value: 'left' },
{ label: __( 'Center', 'my-theme' ), value: 'center' },
{ label: __( 'Right', 'my-theme' ), value: 'right' },
{ label: __( 'Wide', 'my-theme' ), value: 'wide' },
{ label: __( 'Full', 'my-theme' ), value: 'full' },
];
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'my-theme' ) }>
<SelectControl
label={ __( 'Alignment', 'my-theme' ) }
value={ attributes.alignment }
options={ alignmentOptions }
onChange={ onChangeAlignment }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<RichText
tagName="p"
value={ attributes.content }
onChange={ onChangeContent }
placeholder={ __( 'Enter your content here...', 'my-theme' ) }
style={ { textAlign: attributes.alignment } }
/>
</div>
</>
);
},
save: ( { attributes } ) => {
const blockProps = useBlockProps.save( {
className: `my-theme-react-example align-${ attributes.alignment }`,
} );
return (
<div { ...blockProps }>
<RichText.Content
tagName="p"
value={ attributes.content }
style={ { textAlign: attributes.alignment } }
/>
</div>
);
},
} );
Block Lab simplifies the process by letting you focus on the React UI for the editor and the `save` function’s output, while it handles the WordPress-specific registration and attribute management. This is ideal for developers who are comfortable with React but want to avoid writing extensive PHP for each block.
2. ACF Blocks (with React Integration)
Advanced Custom Fields (ACF) is a staple for many WordPress developers. With ACF Blocks, you can register custom blocks that leverage ACF fields. While ACF Blocks are primarily PHP-driven for registration and field definition, the “render callback” can be a PHP function that outputs HTML. However, for dynamic editor experiences, you can use ACF’s JavaScript API and integrate React components. This allows you to build complex editor interfaces for your ACF-driven blocks.
The strategy here is to use ACF to define your fields (which are then available in the block editor’s sidebar) and then use React to build the actual block preview and interaction within the editor canvas. ACF provides the data, and React provides the UI.
Example: ACF Block with React Editor Preview
In your theme’s `functions.php` or a plugin:
<?php
add_action( 'acf/init', 'my_theme_register_acf_react_block' );
function my_theme_register_acf_react_block() {
if ( function_exists( 'acf_register_block_type' ) ) {
acf_register_block_type( array(
'name' => 'my-theme/acf-react-example',
'title' => 'ACF React Example Block',
'description' => 'A custom ACF block with a React editor preview.',
'category' => 'theme',
'icon' => 'layout',
'keywords' => array( 'react', 'acf', 'custom' ),
'enqueue_style' => get_template_directory_uri() . '/build/acf-block-editor.css',
'enqueue_script' => get_template_directory_uri() . '/build/acf-block-editor.js',
'render_callback' => 'my_theme_render_acf_react_block',
'supports' => array( 'align' => true ),
) );
}
}
/**
* Render callback for the ACF React block.
* This function is called on the frontend.
* For the editor preview, the JS will handle it.
*/
function my_theme_render_acf_react_block( $block, $content = '', $is_preview = false ) {
// ACF fields are available via get_field()
$heading = get_field( 'block_heading' ) ?: 'Default Heading';
$text = get_field( 'block_text' ) ?: 'Default text content.';
$align = get_field( 'align' ) ?: 'none'; // ACF fields can be mapped to block supports
// Get alignment class if align is supported and set
$wrapper_attributes = '';
if ( ! empty( $block['align'] ) ) {
$wrapper_attributes = ' class="wp-block-my-theme-acf-react-example align' . esc_attr( $block['align'] ) . '"';
}
if ( $is_preview ) {
// In preview mode, the JS will render the block.
// This PHP callback is primarily for the frontend.
// You might return a placeholder or basic HTML if JS fails.
return '<p>Loading ACF React Block Preview...</p>';
}
?>
<div
And in your React application (`src/acf-block-editor.js`):
import { registerBlockType } from '@wordpress/blocks';
import { InspectorControls, useBlockProps, RichText } from '@wordpress/block-editor';
import { PanelBody, TextControl, TextareaControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
// ACF Block Editor Preview Component
const AcfReactBlockEdit = ( { attributes, setAttributes, clientId } ) => {
// Fetch ACF fields using ACF's JS API (if needed for complex previews)
// For simpler previews, you might pass data via attributes or use a direct render callback
// that ACF's JS preview mechanism can interpret.
// The 'attributes' object here might not directly contain ACF field values unless explicitly passed.
// A common pattern is to use ACF's JS API to fetch field values for the preview.
// For demonstration, let's assume we're using RichText for the main content
// and InspectorControls for ACF fields.
// In a real scenario, you'd likely use wp.data.select('core/editor').getEditedPostAttribute('meta')
// or ACF's specific JS API to get field values.
// A more robust approach involves ACF's render_callback handling the preview via JS.
// However, for a direct React component preview:
const blockProps = useBlockProps( {
className: `wp-block-my-theme-acf-react-example align${ attributes.align || 'none' }`,
} );
// Placeholder for ACF field values - in a real app, fetch these.
const [ heading, setHeading ] = React.useState( __( 'Default Heading', 'my-theme' ) );
const [ text, setText ] = React.useState( __( 'Default text content.', 'my-theme' ) );
// You would typically use wp.data.select('core/editor').getBlockAttributes(clientId)
// and potentially ACF's JS API to get the actual field values for the preview.
// For simplicity, we'll use state here, but this state would need to be synced
// with ACF field values.
// Example of how you might update attributes that map to ACF fields
const onChangeHeading = ( newHeading ) => {
setHeading( newHeading );
// This would need to update the underlying ACF field value,
// which is complex and often handled by ACF's own preview mechanism.
// For direct React preview, you might need to pass these values to the save function.
// setAttributes( { block_heading: newHeading } ); // If block attributes map to ACF fields
};
const onChangeText = ( newText ) => {
setText( newText );
// setAttributes( { block_text: newText } );
};
// The actual ACF fields are managed by ACF. The React component here
// is primarily for the *preview* and *editor interaction*.
// The InspectorControls are where you'd typically interact with ACF fields.
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Block Settings', 'my-theme' ) }>
<TextControl
label={ __( 'Block Heading', 'my-theme' ) }
value={ heading }
onChange={ onChangeHeading }
/>
<TextareaControl
label={ __( 'Block Text', 'my-theme' ) }
value={ text }
onChange={ onChangeText }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<h2>{ heading }</h2>
<p>{ text }</p>
<small>{ __( '(Editor Preview)', 'my-theme' ) }</small>
</div>
</>
);
};
// Register the block type. ACF handles the registration of the block itself.
// This JS is for the editor experience.
// The 'name' here should match the ACF block name, prefixed with 'acf/'.
registerBlockType( 'acf/my-theme-acf-react-example', {
edit: AcfReactBlockEdit,
// The save function is typically handled by ACF's PHP render_callback
// or by ACF's own save mechanism for fields.
// If you need custom save logic for the block wrapper, you might define it here.
save: ( { attributes } ) => {
// This save function is often bypassed by ACF's render_callback.
// If you need to output something specific here for the frontend
// that isn't handled by PHP, you can.
// However, ACF usually manages the output based on field values.
return null; // Let ACF handle the saving/rendering.
},
} );
This method is powerful for developers already invested in ACF. It allows them to extend their existing ACF workflows with dynamic React-powered editor interfaces, providing a much richer user experience for content creators.
3. Kadence Blocks Pro (with React Components)
Kadence Blocks is a popular suite of Gutenberg blocks. While the free version offers a lot, Kadence Blocks Pro unlocks advanced features, including the ability to create custom blocks using their framework. This framework is built with React and provides a streamlined way to develop custom blocks without deep dives into the WordPress block API. It offers a visual builder and a component library that abstracts away much of the boilerplate.
For developers, Kadence Blocks Pro offers a "Code Block" and the ability to extend their existing blocks or create new ones using React. This is less about writing a full React app from scratch and more about leveraging Kadence's React-based infrastructure to build custom elements.
Example: Using Kadence Blocks Pro's Framework for Customization
Kadence Blocks Pro doesn't expose a direct "register your React component" API in the same way as Block Lab. Instead, you extend its capabilities. For instance, you might use their "Advanced Button" block and customize its behavior or appearance via hooks and filters, or use their "Code Block" to inject React components. A more direct custom block creation would involve their Pro features, which are often configured through their UI or specific Pro add-ons.
A common pattern is to use the "HTML" block or a custom "Code" block provided by Kadence Pro, and then enqueue your own React script that targets specific elements or initializes components.
<?php
/**
* Enqueue a custom React script for Kadence Blocks integration.
*/
function my_theme_enqueue_kadence_react_script() {
// Only enqueue in the editor and if Kadence Blocks is active.
if ( ! is_admin() || ! class_exists( 'Kadence_Blocks' ) ) {
return;
}
$asset_file = include( get_template_directory() . '/build/kadence-custom.asset.php' );
wp_enqueue_script(
'my-theme-kadence-react-script',
get_template_directory_uri() . '/build/kadence-custom.js',
$asset_file['dependencies'],
$asset_file['version'],
true // Load in footer
);
// You might pass data to your React script here.
wp_localize_script( 'my-theme-kadence-react-script', 'myThemeKadenceConfig', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
// Add any other configuration needed by your React app.
) );
}
add_action( 'enqueue_block_editor_assets', 'my_theme_enqueue_kadence_react_script' );
?>
In your React application (`src/kadence-custom.js`):
import React from 'react';
import ReactDOM from 'react-dom';
// Example: A simple React component to be initialized on a specific element.
const MyKadenceInteractiveComponent = ( { element } ) => {
const [ count, setCount ] = React.useState( 0 );
return (
<div className="kadence-interactive-wrapper">
<p>This is an interactive React component within Kadence Blocks.</p>
<p>Count: { count }</p>
<button onClick={ () => setCount( count + 1 ) }>Increment</button>
</div>
);
};
// Find all elements that should be initialized with our React component.
document.addEventListener( 'DOMContentLoaded', () => {
const elementsToInitialize = document.querySelectorAll( '.kadence-interactive-target' );
elementsToInitialize.forEach( ( element ) => {
// You might pass data from the element's data attributes to the component.
// const initialData = JSON.parse( element.dataset.initialData || '{}' );
ReactDOM.render( <MyKadenceInteractiveComponent element={ element } />, element );
} );
} );
// Note: For actual custom block creation within Kadence Blocks Pro's framework,
// you'd follow their specific documentation for extending or creating blocks,
// which often involves their own component system and registration methods.
// This example shows how to inject React into a Kadence-managed environment.
This approach is best for developers who want a robust block system with extensibility, and are willing to work within Kadence's ecosystem. It's less about raw block API programming and more about leveraging a powerful, pre-built React-based framework.
4. wp-blocks-react (for Standalone Block Development)
The wp-blocks-react library is a fascinating project that aims to allow you to write Gutenberg blocks entirely in React, without needing to touch much PHP for the block definition itself. It provides a React-like API to define block attributes, edit functions, and save functions. This library then compiles your React code into standard JavaScript that Gutenberg understands.
This is perhaps the most "React-native" way to build Gutenberg blocks. It abstracts the WordPress block API significantly, allowing developers to focus on React patterns. However, it requires a robust build process (like Webpack or Vite) and a good understanding of how it bridges React concepts to WordPress block concepts.
Example: Using wp-blocks-react
First, you'd need to set up a project with a build tool and install the library:
# Using npm
npm install wp-blocks-react @wordpress/blocks --save
# Using yarn
yarn add wp-blocks-react @wordpress/blocks
Then, define your block in a React file (e.g., `src/blocks/MyReactBlock.js`):
import React from 'react';
import { registerBlockType } from '@wordpress/blocks';
import {
RichText,
InspectorControls,
useBlockProps
} from '@wordpress/block-editor';
import { PanelBody, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
// Import the wp-blocks-react library
import { createBlock } from 'wp-blocks-react';
// Define block attributes and components using wp-blocks-react's API
const MyReactBlock = createBlock( {
name: 'my-theme/wp-blocks-react-example',
title: 'WP-Blocks-React Example',
icon: 'smiley',
category: 'theme',
attributes: {
content: {
type: 'string',
default: '',
},
title: {
type: 'string',
default: 'Default Title',
},
},
edit: ( { attributes, setAttributes } ) => {
const blockProps = useBlockProps();
const onChangeContent = ( newContent ) => {
setAttributes( { content: newContent } );
};
const onChangeTitle = ( newTitle ) => {
setAttributes( { title: newTitle } );
};
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'my-theme' ) }>
<TextControl
label={ __( 'Block Title', 'my-theme' ) }
value={ attributes.title }
onChange={ onChangeTitle }
/>
</PanelBody>
</InspectorControls>
<div { ...blockProps }>
<h2>{ attributes.title }</h2>
<RichText
tagName="p"
value={ attributes.content }
onChange={ onChangeContent }
placeholder={ __( 'Enter your content here...', 'my-theme' ) }
/>
</div>
</>
);
},
save: ( { attributes } ) => {
const blockProps = useBlockProps.save();
return (
<div { ...blockProps }>
<h2>{ attributes.title }</h2>
<RichText.Content tagName="p" value={ attributes.content } />
</div>
);
},
} );
// Register the block using the standard WordPress API
registerBlockType( MyReactBlock.name, MyReactBlock );
You would then need to configure your build process to compile this React code into a JavaScript file that WordPress can enqueue. This library aims to make block development feel more like standard React component development, which can be a significant productivity boost for React developers.
5. Create Block (Official WordPress Tool)
While not a plugin, @wordpress/create-block is the official scaffolding tool from the WordPress team for creating new blocks. It sets up a complete development environment, including Webpack, Babel, and all necessary dependencies. Crucially, it defaults to a JavaScript/React-based structure for the block's editor interface.
Using create-block is the recommended way to start building custom blocks from scratch if you want to leverage React and the full WordPress block development ecosystem. It provides a solid foundation and adheres to WordPress best practices.
Example: Scaffolding a New Block with Create Block
First, ensure you have Node.js and npm/yarn installed. Then, run the following command in your terminal:
# Install create-block globally (if not already installed)
npm install -g @wordpress/create-block
# Navigate to your theme or plugin directory
cd /path/to/your/theme/or/plugin
# Create a new block named 'my-custom-block'
create-block my-custom-block
This will create a new directory named `my-custom-block` with a full WordPress plugin structure. Inside, you'll find:
src/index.js: The main entry point for your block's JavaScript, typically written in React.src/edit.js: Contains the React component for the block's editor interface.src/save.js: Contains the React component for the block's saved (frontend) output.build/: The compiled JavaScript and CSS files.package.json: For managing dependencies and build scripts.
You can then navigate into the new directory and start developing:
cd my-custom-block npm start # Or yarn start - for development with live reloading npm run build # For production build
The `src/edit.js` file will look something like this:
/**
* React component for the block editor.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
*
* @param {Object} props Properties passed to the block.
* @param {Object} props.attributes Available block attributes.
* @param {Function} props.setAttributes Function that updates block attributes.
* @return {Element} Element to render.
*/
function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps(); // Applies necessary classes and attributes
return (
<div { ...blockProps }>
<RichText
tagName="h2"
value={ attributes.headline }
onChange={ ( newHeadline ) => setAttributes( { headline: newHeadline } ) }
placeholder={ __( 'Enter headline...', 'my-custom-block' ) }
/>
<RichText
tagName="p"
value={ attributes.content }
onChange={ ( newContent ) => setAttributes( { content: newContent } ) }
placeholder={ __( 'Enter content...', 'my-theme' ) }
/>
</div>
);
}
And `src/save.js`:
/**
* React component for the saved block content.
*
* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save
*
* @param {Object}