Build a Reusable Gutenberg Block with React: From Setup to Styling
Learn how to create a custom Gutenberg block with React, register it via PHP, style it with CSS modules, and verify its output in the editor and front‑end. Understand trade‑offs and next steps for reusable UI components.
13 Apr 2026, 16:54 UTC

Concrete Problem: Editor Consistency
When a team builds a site on WordPress, the Gutenberg editor often ends up with a handful of ad‑hoc blocks that duplicate effort. Each time a new page needs a “Feature Highlight” or a “Customer Testimonial”, developers copy‑paste the same markup, CSS, and sometimes JavaScript, creating maintenance headaches and inconsistent UX.
Thesis
Creating a custom Gutenberg block with React lets you encapsulate that UI once, expose it through the editor, and reuse it across any page or post. The block’s logic lives in a single component, its styles are scoped, and it can be shipped as part of a theme or plugin.
1. Environment & Tooling
- WordPress 6.5+ (or any version that ships with Gutenberg 12+)
- Node 18+ and npm (or yarn)
- WP-CLI for quick scaffolding (optional)
Why React?
Gutenberg’s editor is built on React. Using the same stack means you can leverage hooks, state, and the vast ecosystem of @wordpress packages.
2. Project Structure
For a plugin‑based block, create the following minimal layout:
my-block-plugin/
├─ src/
│ ├─ index.js
│ ├─ edit.js
│ ├─ save.js
│ └─ style.scss
├─ build/
├─ block.json
└─ my-block-plugin.php
3. block.json – Declarative Metadata
This JSON file tells WordPress about the block’s name, category, icon, and assets. It also registers the build scripts for the editor and front‑end.
{
"apiVersion": 2,
"name": "my-plugin/feature-highlight",
"title": "Feature Highlight",
"category": "layout",
"icon": "star",
"description": "A reusable highlight block with image and text.",
"supports": {"align": ["wide", "full"]},
"attributes": {
"title": {"type": "string", "source": "html", "selector": "h3"},
"content": {"type": "string", "source": "html", "selector": "p"},
"imageUrl": {"type": "string", "source": "attribute", "selector": "img", "attribute": "src"}
},
"editorScript": "file:build/index.js",
"editorStyle": "file:build/index.css",
"style": "file:build/style.css"
}
4. PHP Registration – Hook into init
Place this in my-block-plugin.php and activate the plugin.
Risk: Registering before the init hook will skip Gutenberg’s script enqueueing, causing the block to break. Always use init or later.
5. Edit Component – Live Preview
Placed in src/edit.js, this component runs inside the editor. It uses @wordpress/block-editor components to provide a live preview.
import { useBlockProps, RichText, MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
export default function Edit( { attributes, setAttributes } ) {
const blockProps = useBlockProps();
return (
setAttributes( { imageUrl: media.url } ) }
allowedTypes={ [ 'image' ] }
value={ attributes.imageUrl }
render={ ({ open }) => (
Select Image
) }
/>
{ attributes.imageUrl && }
setAttributes( { title } ) }
/>
setAttributes( { content } ) }
/>
);
}
6. Save Component – Static Markup
Placed in src/save.js, this outputs the markup that will appear on the front‑end.
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Save( { attributes } ) {
const blockProps = useBlockProps.save();
return (
{ attributes.imageUrl && }
);
}
7. Styling – Scoped CSS Modules
Create src/style.scss and compile it with npm run build (using @wordpress/scripts or your own Webpack config). The resulting CSS will be namespaced automatically by Gutenberg.
.wp-block-my-plugin-feature-highlight {
background: #f9f9f9;
padding: 1rem;
text-align: center;
}
.wp-block-my-plugin-feature-highlight img {
max-width: 100%;
height: auto;
}
8. Practical Example – Verification Steps
- Activate the plugin via the admin screen.
- Open a page in Gutenberg.
- Search for “Feature Highlight” in the block picker; the block should appear under Layout.
- Insert it, click “Select Image”, and add a title and paragraph.
- See the live preview update as you type.
- Publish the page, view it, and inspect the markup: you should see
<div class="wp-block-my-plugin-feature-highlight">with the image,<h3>, and<p>elements. - Open the browser console and confirm no JavaScript errors from the block.
Trade‑offs & Limitations
- Static vs Dynamic: The example uses a static block, which is fast but cannot pull data from the server after page load. If you need server‑side rendering (e.g., an external API call), you’ll need a dynamic block and must cache the output to avoid performance hits.
- Namespace Collisions: Block names are global. Using
my-plugin/as a prefix reduces the risk, but always check that no other plugin declares the same slug. - Editor Dependencies: The block relies on Gutenberg’s React packages. If you ship the block as part of a theme, ensure the theme doesn’t disable Gutenberg or enqueue conflicting scripts.
- Scoping CSS: Gutenberg automatically scopes block styles, but if you manually add global CSS (e.g., in
style.css) you must use the block’s unique class to avoid leaking styles.
Actionable Next Steps
- Move the block into a shared plugin if you plan to use it across multiple sites.
- Add more attributes (e.g., button URL) and use
InspectorControlsto expose them in the sidebar. - Experiment with
serverSideRenderto create a dynamic block that pulls data from a headless API. - Use
wp-clito scaffold a block:wp scaffold block my-plugin/feature-highlight --themeand adapt the generated files. - Publish the block to the Gutenberg block directory to share with the community.
Conclusion
By following this workflow, you turn repetitive UI into a single, maintainable block. The React component gives you instant live previews, the block.json keeps metadata tidy, and scoped CSS ensures your block’s look stays consistent across the site. While static blocks are quick to implement, consider dynamic rendering for content that changes frequently or comes from external sources.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.