Skip to content

Use Case: Retheme a Hero Banner

A client wants Hero Banner in their brand, Split as the go-to layout, and a promo treatment the component doesn't ship. Four levels of intervention, lightest first, following the priority order.

See it live

Where the walkthrough ends: step 4's promo card on step 1's brand tokens. Switch Layout to Split for step 3, or the theme to Bootstrap theme for the shipped look.

1. Reskin it: --theme-*, not a template override

Hero Banner ships pre-compiled, so it's reskinned from the theme's :root, with no component file touched:

:root {
  --theme-color-primary: #10564f;
  --theme-radius: 0.75rem;
  --theme-container-lg: 1100px;
  /* …the rest of the contract — see Token reference */
}

--theme-radius sets the CTA's corners, through var(--hero-banner-cta-radius, var(--theme-radius, 0)). Bootstrap has no runtime variable for it, so the token is the only lever. The CTA's colour is a stock .btn-outline-light / -dark, so it follows $light / $dark like every other button on the site.

2. A component-scoped token when only Hero Banner should move

When the shared values are right everywhere else, set a --hero-* token instead, scoped to a class, never :root:

Token Controls
--hero-min-height-mobile / -desktop Minimum height per breakpoint (defaults below)
--hero-gradient-vertical The legibility scrim under light-variant text
--hero-carousel-control-width Width of the prev/next hit area
--hero-split-container-inset Split's content inset from the viewport edge
--hero-split-media-min-height Split's media-column minimum height
.hero-banner--split {
  --hero-split-media-min-height: 320px;
}

Each layout sets its own min-heights, so override them on .hero-banner rather than assuming one default:

Layout Mobile Desktop
Simple 360px 480px
Full 80dvh 90dvh
Split auto (content sets it) 600px

Suggested image aspect ratios

Backgrounds are object-fit: cover, so any image works. These crops flatter each layout most:

Layout Mobile Desktop
Simple 1:1 3:1
Full 9:16 16:9
Split 16:9 4:3

Shoot looser than you need: a 4:3 photo crops cleanly to 16:9, but not the other way round.

3. Pick an existing layout, not the shipped default

layout is a prop (simple | full | split, default simple), so editors pick Split per instance in Canvas or Layout Builder, with nothing to build. Making Split the site-wide default is the smallest version of step 4: override the component and restate only layout with a new default:.

4. Add a layout the component doesn't ship

A compact promo card pinned to a corner of the image needs a new enum value, its own markup and its own SCSS:

# themes/custom/<theme>/components/hero_banner/hero_banner.component.yml
name: Hero Banner
replaces: 'ixm_blocks:hero_banner'
props:
  type: object
  properties:
    layout:
      type: string
      enum: [simple, full, split, promo-card]
      default: simple
libraryOverrides:
  css:
    component:
      public/css/hero-banner.css: { minified: true }
{# themes/custom/<theme>/components/hero_banner/hero_banner.twig #}
{# The existing layout_classes already yield .hero-banner--promo-card; only
   the markup needs a branch, one that skips the carousel wrapper. #}
{% if layout == 'promo-card' %}
  <div{{ create_attribute().addClass(layout_classes).addClass('position-relative') }}>
    {{ slides }}
  </div>
{% else %}
  {# …existing simple/full/split carousel markup, unchanged… #}
{% endif %}
// themes/custom/<theme>/components/hero_banner/src/scss/hero-banner.scss
@use "sass:map";
@import "@theme/global/src/scss/abstracts/scaffold";

// Slides still carry .carousel-item, which floats; outside .carousel-inner
// that collapses the wrapper to nothing.
.hero-banner--promo-card .carousel-item {
  float: none;
  margin-right: 0;
}

// The content block carries Bootstrap's .position-relative and .py-5
// utilities, both !important, so position and padding need it too.
.hero-banner--promo-card .hero__banner .hero__banner-content {
  position: absolute !important;
  inset-block-end: map.get($spacers, 4);
  inset-inline-end: map.get($spacers, 4);
  max-width: 20rem;
  padding: map.get($spacers, 4) !important;
  background: $primary;
  border-radius: $border-radius-lg;
}

// The slide's content column is .col-lg-8 — two-thirds of a 20rem card.
.hero-banner--promo-card .hero__banner .hero__banner-content .row > * {
  flex: 0 0 100%;
  max-width: 100%;
}

sdc_prop_inherit saves retyping the other props; see Frameworks. Reaching for $primary directly is fine here: this file lives in the project theme, where a brand palette belongs.

The shape of it

Change Lever Priority-order tier
Brand colours, radius, spacing across every component --theme-* in the theme's :root 2 — escape hatch
Only Hero Banner's gradient, min-height or split inset A --hero-* component token, scoped to a class 2
A different shipped layout, per instance The layout prop, set in Canvas or Layout Builder Authoring, not theming
A different shipped layout, as the new default everywhere replaces: override, prop schema only 2, unchanged markup
A layout the component doesn't have at all replaces: override with new markup and its own SCSS 1 — Bootstrap Sass, direct