Hero Block
A full-width hero section with heading, subheading, image, rich text description, and a call-to-action button. Demonstrates multiple field types in a single block: string, textarea, slate, image, and object_browser.
Welcome to Our Site
Discover amazing content across multiple lines
We build tools that make content editing delightful.
Developer Reference
Schema
Pass this object inside the blocks option when calling initBridge() to register this block type with the admin UI. See Custom Blocks for the full setup guide.
{
"fieldsets": [
{
"id": "default",
"title": "Default",
"fields": [
"heading",
"subheading",
"buttonText",
"buttonLink",
"image",
"description"
]
}
],
"properties": {
"heading": {
"title": "Heading",
"type": "string",
"placeholder": "Enter hero heading…"
},
"subheading": {
"title": "Subheading",
"type": "string",
"widget": "textarea",
"placeholder": "Enter subheading…"
},
"buttonText": {
"title": "Button Text",
"type": "string"
},
"buttonLink": {
"title": "Button Link",
"widget": "object_browser",
"mode": "link",
"allowExternals": true
},
"image": {
"title": "Image",
"widget": "image"
},
"description": {
"title": "Description",
"type": "array",
"widget": "slate"
}
},
"required": []
}JSON Block Data
Example JSON as stored in the Plone content API. This is the data structure your component will receive in the block prop.
{
"@type": "hero",
"heading": "Welcome to Our Site",
"subheading": "Discover amazing content\nacross multiple lines",
"buttonText": "Get Started",
"buttonLink": [
{
"@id": "/docs/frontend-guide/build-a-frontend"
}
],
"image": "data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27800%27 height=%27400%27%3E%3Crect width=%27100%25%27 height=%27100%25%27 fill=%27%234a90d9%27/%3E%3Ctext x=%2750%25%27 y=%2750%25%27 fill=%27white%27 text-anchor=%27middle%27 font-size=%2724%27%3EHero Image%3C/text%3E%3C/svg%3E",
"description": [
{
"type": "p",
"children": [
{
"text": "We build tools that make content editing delightful."
}
]
}
]
}Rendering
How this block renders in your frontend. Add its handling to your renderer, or — for list-style blocks — register a fetcher and reuse your list rendering.
import { getImageUrl } from './utils.js';
function HeroBlock({ block }) {
const subheading = (block.subheading || '').replace(/\n/g, '<br>');
const buttonLink = block.buttonLink?.[0]?.['@id'] || '';
const imageSrc = getImageUrl(block.image);
// Data-driven: render a field only when it has data. No data ⇒ no element, so
// view markup stays clean. Hydra reveals an empty optional field for editing by
// seeding it, which makes these same checks true — no edit-mode branch needed.
return (
<div data-block-uid={block['@uid']} className="hero-block">
{imageSrc && (
<img data-edit-media="image" src={imageSrc} alt="Hero image" />
)}
{block.heading && <h1 data-edit-text="heading">{block.heading}</h1>}
{block.subheading && (
<p data-edit-text="subheading" dangerouslySetInnerHTML={{ __html: subheading }} />
)}
{block.description && (
<div className="hero-description" data-edit-text="description">
{block.description.map((node, i) => (
<SlateNode key={i} node={node} />
))}
</div>
)}
{(block.buttonText || block.buttonLink) && (
<a data-edit-text="buttonText" data-edit-link="buttonLink" href={buttonLink}>
{block.buttonText}
</a>
)}
</div>
);
}