Build an Editable Hero Block
Create a reusable hero section with inline-editable text, save it in WordPress, and verify its generated PHP output.
Build a hero section that editors can insert into a Page, change directly in Gutenberg, and reuse with different text. You will author the layout in React and export it as a native WordPress block.
What you will build
Part Editor experience Storage Heading Edit text in the block canvas The block instance's heading attributeDescription Edit text in the block canvas The block instance's description attributeLayout and colors Defined in the theme source Tailwind classes in the TSX file Whole section Insert, duplicate, or move where Page settings permit The Page's saved block content |
|---|
Each inserted hero has its own text. Editing one instance leaves the others unchanged.
Before you start
You need a ForgeWP theme using the React adapter, its dependencies installed, and a WordPress test site for the exported theme. Run commands from the theme root: the directory containing package.json and wp.config.ts.
If you are setting up your first project, complete Create a New Theme first. For the underlying concepts, read Create Your First Gutenberg Block and Choose How Block Content Is Edited.
1. Create the block file
pnpm forgewp make:block HeroBlock --attributes=heading,descriptionThis creates src/blocks/HeroBlock.tsx. If that file already exists, use it for this recipe or choose a fresh theme project. The command scaffolds the block definition; you will insert the exported block through WordPress in step 5.
2. Add the editable hero
Replace the generated file with this complete example:
1import { defineBlock, WpEditable } from "@forgewp/react"
2
3export default defineBlock({
4 name: "hero-block",
5 title: "Editable Hero",
6 category: "design",
7 icon: "cover-image",
8 attributes: {
9 heading: {
10 type: "string",
11 label: "Heading",
12 default: "A quieter place to stay",
13 },
14 description: {
15 type: "string",
16 label: "Description",
17 default: "Comfortable rooms, thoughtful details, and time to unwind.",
18 },
19 },
20 edit: ({ attributes, setAttributes }) => (
21 <section className="bg-slate-950 px-6 py-16 text-white md:px-12 md:py-24">
22 <div className="mx-auto max-w-4xl">
23 <WpEditable
24 tagName="h2"
25 value={attributes.heading}
26 onChange={(value) => setAttributes({ heading: value })}
27 className="text-4xl font-bold tracking-tight md:text-6xl"
28 />
29 <WpEditable
30 tagName="p"
31 value={attributes.description}
32 onChange={(value) => setAttributes({ description: value })}
33 className="mt-6 max-w-2xl text-lg leading-relaxed text-slate-300"
34 />
35 </div>
36 </section>
37 ),
38 save: ({ attributes }) => (
39 <section className="bg-slate-950 px-6 py-16 text-white md:px-12 md:py-24">
40 <div className="mx-auto max-w-4xl">
41 <WpEditable
42 tagName="h2"
43 value={attributes.heading}
44 className="text-4xl font-bold tracking-tight md:text-6xl"
45 />
46 <WpEditable
47 tagName="p"
48 value={attributes.description}
49 className="mt-6 max-w-2xl text-lg leading-relaxed text-slate-300"
50 />
51 </div>
52 </section>
53 ),
54})The utility classes give the hero a dark background, readable spacing, and a heading that grows on wider screens. You can change these classes to match your theme.
This reusable section uses an h2. Choose its heading level in the context of the finished Page; if it becomes the Page's main heading, coordinate it with the template's title and heading structure.
3. Understand the data flow
Piece Responsibility defineBlock()Declares a block the compiler can discover and register attributesNames the saved values and supplies their initial defaults editDescribes the Gutenberg editing view WpEditableProvides inline text editing for the selected value setAttributesWrites an editor's change back to the block attributes saveSupplies visitor markup for ForgeWP to compile into the PHP renderer |
|---|
In edit, changing the heading calls setAttributes({ heading: value }). WordPress saves that value with this block instance when the editor saves the Page. At request time, the generated PHP renderer uses the saved attributes to produce the visitor's HTML.
The two layouts are shown explicitly so you can see the editor and visitor responsibilities. Keep their classes and content bindings aligned as you extend the block. Here, save is ForgeWP authoring input for a dynamic PHP-rendered block.
4. Export the theme
pnpm typecheck
pnpm forgewp export --validate --strictThe standard starter provides the typecheck script. If you use a customized project, run its equivalent TypeScript check before exporting.
Look inside .forgewp/out/<theme-slug>/ for:
blocks/
hero-block/
block.json
render.php
assets/
forgewp-editor.js<theme-slug> means your theme's generated directory name. In block.json, check the registered name forgewp/hero-block and the heading and description attributes. The theme's shared editor script provides the Gutenberg controls.
Local Vite preview is useful for theme styling. This recipe's block registration, inline controls, and saved block instances are verified in WordPress after export.
5. Insert and edit it in WordPress
- Install and activate the exported ZIP on your test site. See Install and Verify for the upload steps.
- Create a test Page called Hero Demo.
- Choose ForgeWP Builder as the Page template. This template renders the Page's saved blocks. You can also use a template that includes a
BlockAreacontent slot. - Open the block inserter, search for Editable Hero, and insert it.
- Click the heading and description in the canvas and enter your own text.
- Save or publish the Page, reload the editor, and view the visitor page.
The Page body stores the block instance. A developer-authored page template can include this body through a content slot; selecting the Builder template gives editors a canvas for composing the whole body. See Content Ownership and Editability.
6. Verify the result
- The inserter offers Editable Hero.
- The heading and description are editable in the canvas.
- Saved changes survive reloading the editor and appear on the visitor page.
- Duplicating the hero gives you another instance whose text you can change independently.
- Moving the block changes the section order where the Page permits movement.
- The visitor sees rendered content, without inline editing controls.
- The hero remains readable on a narrow screen and a desktop screen.
Try a small maintenance check too: save custom text in WordPress, change only a spacing class in the source, export again, and update the test theme. Confirm that the saved text remains connected to the same block attributes. Keep the block name and attribute names stable for this check.
Common questions
What you see What to check The block is missing from the inserter Confirm the generated block files are in the installed theme and that the Page allows this block Editing the text has no lasting effect Check that value reads the correct attribute and onChange calls setAttributesThe editor changes but the visitor text stays the same Check the corresponding save binding, confirm the Page was saved, and refresh the site's relevant cachesThe Page displays a ready-made layout instead Choose ForgeWP Builder or a template that renders the saved Page body The new block does not appear on the Vite homepage Insert and test it in WordPress; the scaffold creates the block source rather than adding a section to the homepage |
|---|
Practice next
Continue to Add an Editable CTA and Image. You will extend this same block with a link and a Media Library image, then check the result against concrete acceptance criteria.