HeroSplitpopular
apps/www/src/sections/HeroSplit.astro · 96 lines · group Hero
HeroSplit — the first screen with the text on one side and the picture on the other. Use for: a product with a picture worth half the screen — a device, a packshot, a screenshot. Not for: a wide photo as a backdrop (<HeroCover>); no picture at all (<HeroText>). Children: optional markdown between the tags, rendered under the description and above the buttons. No sub-block tags. Picture: `imageFit` decides how it looks. imageFit="contain" (default) shows the picture whole, hugging the bottom edge of its half. Only a PORTRAIT or square picture looks right; a landscape one sinks into the bottom corner and leaves the top of its half empty. Product shots on a transparent or white background work best. imageFit="cover" fills the picture half edge to edge, cropped, with a diagonal that hands the text band over to the photo. Use it for a LANDSCAPE photo. Theme: "light" (white) or "dark" (stone band with white text). imageSide puts the picture left or right. Buttons: a button appears only when its text is given.
In the fleet
Used on 3 sites built with this engine.
Samples 2
Nordvind Station N3Two-year battery, one bolt
Sensors come calibrated and swap in the field without tools. The housing survives the winter outdoors, and the radio reaches the gateway from four kilometres away.

MDX of this sample
<HeroSplit
title="Nordvind Station N3"
highlight="Two-year battery, one bolt"
description="Sensors come calibrated and swap in the field without tools. The housing survives the winter outdoors, and the radio reaches the gateway from four kilometres away."
image="photo-tall-02.jpg"
primaryText="See the models"
primaryHref="/stations/"
secondaryText="Datasheet"
secondaryHref="/downloads/"
/>The season, recordedfrom the first frost to the last harvest
Everything the stations measure is stored from day one, so the season you are having can be compared with the season you had.

MDX of this sample
<HeroSplit
theme="dark"
imageFit="cover"
imageSide="left"
title="The season, recorded"
highlight="from the first frost to the last harvest"
description="Everything the stations measure is stored from day one, so the season you are having can be compared with the season you had."
image="photo-tall-01.jpg"
primaryText="Open the dashboard"
primaryHref="/dashboard/"
/>Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | yes | the headline, one line | |
| highlight | string | a second line of the headline, painted with the brand gradient | ||
| description | string | the pitch under the headline, one or two sentences | ||
| image | string | yes | file name in images/; portrait for imageFit="contain", landscape for "cover" | |
| imageSide | 'left' | 'right' | 'right' | which side the picture takes | |
| theme | 'light' | 'dark' | 'light' | "light" white, "dark" stone band | |
| imageFit | 'cover' | 'contain' | 'contain' | "contain" shows the picture whole (portrait shots); "cover" crops it to fill the half (landscape photos) | |
| primaryText | string | text of the solid brand button; omit to hide the button | ||
| primaryHref | string | '#' | link of the primary button | |
| secondaryText | string | text of the second, outlined button | ||
| secondaryHref | string | '#' | link of the secondary button |
Source apps/www/src/sections/HeroSplit.astro @ gitt.one
---
/**
* HeroSplit — the first screen with the text on one side and the picture on
* the other.
*
* Use for: a product with a picture worth half the screen — a device, a
* packshot, a screenshot.
* Not for: a wide photo as a backdrop (<HeroCover>); no picture at all
* (<HeroText>).
* Children: optional markdown between the tags, rendered under the description
* and above the buttons. No sub-block tags.
* Picture: `imageFit` decides how it looks.
* imageFit="contain" (default) shows the picture whole, hugging the bottom
* edge of its half. Only a PORTRAIT or square picture looks right; a
* landscape one sinks into the bottom corner and leaves the top of its half
* empty. Product shots on a transparent or white background work best.
* imageFit="cover" fills the picture half edge to edge, cropped, with a
* diagonal that hands the text band over to the photo. Use it for a
* LANDSCAPE photo.
* Theme: "light" (white) or "dark" (stone band with white text). imageSide
* puts the picture left or right.
* Buttons: a button appears only when its text is given.
*/
import { Image } from 'astro:assets';
import { resolveImage } from '../lib/images';
import Button from './_Button.astro';
interface Props {
/** the headline, one line */
title: string;
/** a second line of the headline, painted with the brand gradient */
highlight?: string;
/** the pitch under the headline, one or two sentences */
description?: string;
/** file name in images/; portrait for imageFit="contain", landscape for "cover" */
image: string;
/** which side the picture takes */
imageSide?: 'left' | 'right';
/** "light" white, "dark" stone band */
theme?: 'light' | 'dark';
/** "contain" shows the picture whole (portrait shots); "cover" crops it to fill the half (landscape photos) */
imageFit?: 'cover' | 'contain';
/** text of the solid brand button; omit to hide the button */
primaryText?: string;
/** link of the primary button */
primaryHref?: string;
/** text of the second, outlined button */
secondaryText?: string;
/** link of the secondary button */
secondaryHref?: string;
}
const {
title, highlight, description, image: imageName, imageSide = 'right', theme = 'light',
imageFit = 'contain', primaryText, primaryHref = '#', secondaryText, secondaryHref = '#',
} = Astro.props;
const image = await resolveImage(imageName);
const dark = theme === 'dark';
const bleed = imageFit === 'cover';
const left = imageSide === 'left';
---
<section class:list={['relative overflow-hidden', dark ? 'bg-stone-800 text-white' : 'bg-white text-gray-900']}>
<div class:list={['mx-auto max-w-7xl', bleed ? 'lg:grid lg:grid-cols-2' : 'relative w-full px-4 pt-16 text-center sm:px-6 lg:px-8 lg:py-40 lg:text-left']}>
<div class:list={[left && bleed && 'lg:order-2', !bleed && ['mx-auto max-w-[40rem] lg:w-[48%]', left ? 'lg:ml-auto lg:mr-0' : 'lg:mx-0'], bleed && ['px-4 py-16 sm:px-6 sm:py-24 lg:px-8', left ? 'lg:pl-16' : 'lg:pr-16']]}>
<h1 class="text-4xl font-extrabold tracking-tight sm:text-5xl lg:text-6xl">
{title}
{highlight && <span class:list={['block bg-gradient-to-r bg-clip-text text-transparent', dark ? 'from-primary-300 to-primary-alt-400' : 'from-primary-500 to-primary-alt-600']}>{highlight}</span>}
</h1>
{description && <p class:list={['mt-6 text-xl', dark ? 'text-primary-100' : 'text-gray-500']}>{description}</p>}
<slot />
{(primaryText || secondaryText) && (
<div class:list={['mt-10 flex flex-wrap gap-4', !bleed && 'justify-center lg:justify-start']}>
{primaryText && <Button href={primaryHref}>{primaryText}</Button>}
{secondaryText && <Button href={secondaryHref} kind="secondary" onDark={dark}>{secondaryText}</Button>}
</div>
)}
</div>
{!bleed && (
/* the photo hugs the section's bottom edge, as it always did; it is
positioned against the max-w-7xl shell, so it takes half of the
container and not half of the viewport */
<div class:list={['relative mt-8 h-64 w-full lg:absolute lg:inset-y-0 lg:mt-0 lg:h-full lg:w-1/2', left ? 'lg:left-0' : 'lg:right-0']}>
<Image src={image} alt="" widths={[400, 840, 1280]} loading="eager" fetchpriority="high" class:list={['absolute inset-0 h-full w-full object-contain object-center', left ? 'lg:object-left-bottom' : 'lg:object-right-bottom']} />
</div>
)}
{bleed && (
<div class:list={['relative h-64 sm:h-80 lg:h-auto', left && 'lg:order-1']}>
<Image src={image} alt="" widths={[400, 840, 1280]} loading="eager" fetchpriority="high" class="absolute inset-0 h-full w-full object-cover" />
<svg class:list={['absolute inset-y-0 hidden h-full w-24 lg:block', dark ? 'text-stone-800' : 'text-white', left ? 'right-0 translate-x-px' : 'left-0 -translate-x-px']} viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true">
<polygon points={left ? '100,0 100,100 0,100' : '0,0 100,0 0,100'} fill="currentColor" />
</svg>
</div>
)}
</div>
</section>Source links point to engine-astro@7b686388. Samples are demo content: the components are the real ones, the copy and the pictures are made up.