ForgeWP

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

Table · 3 columns
Part
Editor experience
Storage
Heading
Edit text in the block canvas
The block instance's heading attribute
Description
Edit text in the block canvas
The block instance's description attribute
Layout 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

Theme terminal: scaffold the hero block
pnpm forgewp make:block HeroBlock --attributes=heading,description

This 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:

src/blocks/HeroBlock.tsx
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

Table · 2 columns
Piece
Responsibility
defineBlock()
Declares a block the compiler can discover and register
attributes
Names the saved values and supplies their initial defaults
edit
Describes the Gutenberg editing view
WpEditable
Provides inline text editing for the selected value
setAttributes
Writes an editor's change back to the block attributes
save
Supplies 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

Theme terminal: check types and export
pnpm typecheck
pnpm forgewp export --validate --strict

The 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:

Hero block output inside the generated theme
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

  1. Install and activate the exported ZIP on your test site. See Install and Verify for the upload steps.
  2. Create a test Page called Hero Demo.
  3. Choose ForgeWP Builder as the Page template. This template renders the Page's saved blocks. You can also use a template that includes a BlockArea content slot.
  4. Open the block inserter, search for Editable Hero, and insert it.
  5. Click the heading and description in the canvas and enter your own text.
  6. 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

Table · 2 columns
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 setAttributes
The 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 caches
The 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.