Studio Docs

Compose a Hero From Primitives

Five distinct Hero variants built from the same 10 registered primitives. No Hero component, no engineering ticket for the next variant.

Prerequisite reading. This recipe assumes you’ve read Design a component library that composes, not sprawls and registered the 10 primitives (4 atoms + 6 layouts) it describes.

Do It With a Skill

compose-marketing-section takes a design comp or a description and assembles it from primitives you have already registered.

curl -fsSL https://studio-documentation.contentstackapps.com/install.sh | sh

What You’ll Build

Five Hero variants laid out in a grid: centered with background image, split (text left, image right), full-bleed immersive, thumbnail-on-top for blog articles, and two-CTA with primary + secondary buttons. Each is composed of Section + Stack + atoms, with BackgroundMedia, SplitRow, or neither depending on variant.

  • Variant 1: Centered Hero with a background image
  • Variant 2: Split Hero (text left, thumbnail right)
  • Variant 3: Full-bleed Hero (immersive, minimal text)
  • Variant 4: Thumbnail-on-top Hero (blog / product launch)
  • Variant 5: Two-CTA Hero (primary + secondary actions)

All five bind to the same Content Type shape: you write the CT once, and authors pick a variant per Template drop.

Prerequisite: the Content Type

Every Hero binds to fields on a landing_page Content Type. Suggested fields:

Field UIDTypeNotes
hero_headlineSingle-line textBound to Heading.text
hero_descriptionMulti-line textBound to Description.text
hero_cta_labelSingle-line textBound to Button.label
hero_cta_urlLinkBound to Button.href
hero_secondary_cta_labelSingle-line textOptional, for the two-CTA variant
hero_secondary_cta_urlLinkOptional
hero_bg_imageFile (asset)Bound to BackgroundMedia.image
hero_thumb_imageFile (asset)Bound to Image.src for split / thumbnail-top variants

You don’t need every field for every variant: the CT is a superset, each variant binds to the subset it needs.

Variant 1: Centered Hero With Background Image

A Centered Hero variant rendered on canvas: a full-width Section with a dark background image behind, a strong dark overlay over the image, and centered white text stacked in the middle: a large H1 headline, a supporting description below, and a primary CTA button underneath.

Composition tree:

Section (spacing: spacious, contentAlign: center)
└── BackgroundMedia (image: hero-bg.jpg, overlay: strong, height: tall)
    └── Stack (spacing: normal, alignment: center)
        ├── Heading (text: "Ship faster.", level: h1, emphasis: inverse)
        ├── Description (text: "The platform that…", emphasis: inverse)
        └── Button (label: "Start free trial", variant: default, size: lg)

Bindings:

  • Heading.text binds to entry.hero_headline
  • Description.text binds to entry.hero_description
  • Button.label binds to entry.hero_cta_label
  • Button.href binds to entry.hero_cta_url
  • BackgroundMedia.image binds to entry.hero_bg_image.url

Design-system does the rest: H1 sizing, overlay tint, spacing, button color. Every visual token resolves without an author choosing.

Variant 2: Split Hero (text Left, Thumbnail Right)

A Split Hero variant: a Section split into two equal columns via SplitRow. Left column has a Stack of Heading H1, Description, and primary Button, all left-aligned. Right column is a 4:3 thumbnail image, vertically centered next to the text.

Composition tree:

Section (spacing: spacious)
└── SplitRow (ratio: 50-50, verticalAlign: center)
    ├── leftSlot:
    │   └── Stack (spacing: normal, alignment: left)
    │       ├── Heading (text: "…", level: h1)
    │       ├── Description (text: "…")
    │       └── Button (label: "Talk to sales", variant: default, size: lg)
    └── rightSlot:
        └── Image (src: hero-thumb.png, aspect: 4:3, fit: cover)

Bindings:

  • Heading.text binds to entry.hero_headline
  • Description.text binds to entry.hero_description
  • Button.label binds to entry.hero_cta_label, and Button.href binds to entry.hero_cta_url
  • Image.src binds to entry.hero_thumb_image.url

Varieties from the same tree:

  • Change SplitRow.ratio: 60-40 to give the text more room.
  • Swap leftSlot and rightSlot contents for image-left layouts: no schema change, just re-arrange on canvas.
  • Set reverseOnMobile: true if the design wants image-first on small screens.

Variant 3: Full-Bleed Hero (immersive)

A Full-bleed Hero: full viewport height, strong dark overlay over a background image, minimal text: a large left-aligned H1 headline and a single ghost-variant Button with a play icon underneath. No description.

Composition tree:

Section (spacing: compact)
└── BackgroundMedia (image: hero-full.jpg, overlay: strong, height: full)
    └── Stack (spacing: tight, alignment: left)
        ├── Heading (text: "…", level: h1, emphasis: inverse)
        └── Button (label: "Watch the film", variant: ghost, icon: play, size: lg)

Bindings:

  • Heading.text binds to entry.hero_headline
  • Button.label binds to entry.hero_cta_label, and Button.href binds to entry.hero_cta_url
  • BackgroundMedia.image binds to entry.hero_bg_image.url

No Description binding for this variant. Full viewport height. Minimal overlay content: the image carries the message.

Variant 4: Thumbnail-on-Top Hero (blog / Product)

A Thumbnail-on-top Hero: no background image, a wide 16:9 thumbnail image at the top of the Section, followed by an H1 headline, a description, and a link-variant Button with an arrow-right icon, all centered.

Composition tree:

Section (spacing: comfortable, contentAlign: center)
└── Stack (spacing: loose, alignment: center)
    ├── Image (src: article-thumb.jpg, aspect: 16:9, fit: cover)
    ├── Heading (text: "…", level: h1)
    ├── Description (text: "…")
    └── Button (label: "Read the article", variant: link, icon: arrow-right)

No BackgroundMedia. Image sits above the text. Common for blog post headers and product launch pages.

Bindings:

  • Image.src binds to entry.hero_thumb_image.url
  • Heading.text binds to entry.hero_headline
  • Description.text binds to entry.hero_description
  • Button.label binds to entry.hero_cta_label, and Button.href binds to entry.hero_cta_url

Variant 5: Two-CTA Hero

A Two-CTA Hero: Section with a subtle background image overlay, centered Stack containing H1 headline, description, and a nested Stack holding two side-by-side buttons: a primary default variant and an outline variant.

Composition tree:

Section (spacing: spacious, contentAlign: center)
└── BackgroundMedia (image: hero-abstract.jpg, overlay: subtle)
    └── Stack (spacing: normal, alignment: center)
        ├── Heading (text: "…", level: h1, emphasis: inverse)
        ├── Description (text: "…", emphasis: inverse)
        └── Stack (spacing: tight, alignment: center)
            ├── Button (label: "Start free", variant: default, size: lg)
            └── Button (label: "Book a demo", variant: outline, size: lg)

Two buttons in a nested Stack. If you want them strictly side-by-side rather than gap-stacked, extend Stack with a direction: row option or introduce a horizontal Row layout primitive, a one-time addition to the library.

Bindings:

  • Primary Button.label binds to entry.hero_cta_label
  • Primary Button.href binds to entry.hero_cta_url
  • Secondary Button.label binds to entry.hero_secondary_cta_label
  • Secondary Button.href binds to entry.hero_secondary_cta_url
  • Plus Heading, Description, BackgroundMedia bindings as in Variant 1.

Which Layout Props Stay Static Vs. Exposed

  • Static per Section: Section.spacing, SplitRow.ratio, Stack.alignment, BackgroundMedia.overlay. Design decisions the Section author makes once.
  • Exposed Prop candidates: Section.background, BackgroundMedia.overlay, SplitRow.ratio (rare: for Templates that want to override the split from 50-50 to 60-40 on landing pages).
  • Never exposed: atom props. Content flows via CMS bindings. Visual polish stays in the design system.

What You’ll See in Studio

The primitives you registered show up in Studio’s palette on the left of the canvas. Drag one onto the canvas to add it. Select it to configure its semantic options in the properties panel on the right.

Studio's Components palette on the left side of the canvas, with the 'Registered Components' accordion expanded showing tiles for each registered atom and layout: Heading, Description, Button, Image, Section, BackgroundMedia, SplitRow, Stack, Grid, Card. Each tile shows its icon and display name.

Once you drop a Section on the canvas and nest content inside, the composition renders inline with real bindings pulled from the linked Content Type:

Studio's canvas showing a Hero Section rendered with a background image, a large headline overlay, a description, and a primary CTA button. The Layers panel on the right shows the composition tree (a Section holding BackgroundMedia, holding a Stack, holding Heading, Description and Button) and the properties panel is open on the selected Heading node, showing its semantic options (text bound to entry.hero_headline, level h1, emphasis inverse).

Every semantic dropdown (Heading level, Section spacing, Card variant) is a controlled list from your DS: no free text, no drift.

Building This in Studio: Step by Step

  1. Open the Sections palette, click Create Section, name it Hero — Centered.
  2. Set the target Content Type to landing_page (the linked schema).
  3. Drag Section onto the canvas. Configure spacing: spacious, contentAlign: center.
  4. Drag BackgroundMedia into Section’s children slot. Configure overlay: strong, height: tall.
  5. Drag Stack into BackgroundMedia’s foreground slot. Configure spacing: normal, alignment: center.
  6. Drag Heading into Stack. Bind text to landing_page.hero_headline via the Data Picker. Configure level: h1, emphasis: inverse.
  7. Drag Description into Stack, below Heading. Bind text to landing_page.hero_description. Configure emphasis: inverse.
  8. Drag Button into Stack, below Description. Bind label to hero_cta_label, href to hero_cta_url. Configure variant: default, size: lg.
  9. Save the Section. Repeat for other variants: save each as Hero — Split, Hero — Full-bleed, etc.

Template authors then drop the variant that fits their page. Same CT bindings, different composition: no code change per variant.

Testing

Once the Section is saved:

  1. Create a landing_page entry, fill in hero_headline / hero_description / hero_cta_label / hero_cta_url / hero_bg_image.
  2. Create a Template with URL pattern /landing/:slug, drop Hero — Centered on it.
  3. Visit /landing/<slug>. The Hero should render with the entry’s content, backed by the design-system tokens.