Gutenberg blocks are just React components with a WordPress-flavored lifecycle. The key is to keep the block UI predictable, store the right attributes, and ship it in a plugin structure that's easy to maintain.
Block anatomy (what I always start with)
- block.json: block name, title, category, attributes, editorScript/style.
- Edit component: editor-only UI (controls, previews, placeholders).
- Save function: front-end markup output (or use dynamic/PHP render when needed).
- Attributes: store the minimum data needed to reproduce the output.
{
"apiVersion": 2,
"name": "my-plugin/feature-card",
"title": "Feature Card",
"category": "widgets",
"attributes": {
"title": { "type": "string", "default": "" },
"description": { "type": "string", "default": "" }
},
"supports": { "html": false }
}json
Reusable UI patterns
For real projects, you don't want every block to re-implement the same inspector controls, field components, or layout helpers. I typically extract small UI components (inputs, toggles, color pickers) and keep block-specific logic in the block folder.
- Create small UI primitives (TextControl wrappers, ToggleControl wrappers).
- Share utility hooks (e.g., safe attribute updates, debounced input).
- Keep markup and styling consistent across blocks.
- Plan attributes carefully so saved content remains stable over time.
Shipping inside a plugin
In production, the most important part is maintainability: clear folder structure, versioned builds, and predictable registration. Once you do that, scaling from 1 block to 20 blocks becomes a tooling problem—not a code mess.