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.


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.

{
  "hero": {
    "blockSchema": {
      "properties": {
        "heading": {
          "title": "Heading"
        },
        "subheading": {
          "title": "Subheading",
          "widget": "textarea"
        },
        "buttonText": {
          "title": "Button Text"
        },
        "buttonLink": {
          "title": "Button Link",
          "widget": "object_browser",
          "mode": "link",
          "allowExternals": true
        },
        "image": {
          "title": "Image",
          "widget": "image"
        },
        "description": {
          "title": "Description",
          "widget": "slate"
        }
      }
    },
    "fieldMappings": {
      "@default": {
        "title": "heading",
        "description": "subheading",
        "@id": "buttonLink",
        "image": "image"
      }
    }
  }
}

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": "/getting-started"
    }
  ],
  "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. Inka 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>
  );
}