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.

Hero image

Welcome to Our Site

Discover amazing content across multiple lines

We build tools that make content editing delightful.

Get Started

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>
  );
}