Composables

PageHero

A multi-part landing-section primitive composed of a section container plus headline, title, description, actions, links, image, and backdrop sub-recipes. Supports light, dark, and neutral colors with size, orientation, and alignment axes.

Overview

The PageHero is a multi-part landing-section primitive used for the introduction at the top of a marketing or product page. It is composed of eight recipe parts: usePageHeroRecipe() for the <section> container, usePageHeroHeadlineRecipe() for the wrapper around eyebrow/title/description, usePageHeroTitleRecipe() and usePageHeroDescriptionRecipe() for the typography, usePageHeroActionsRecipe() and usePageHeroLinksRecipe() for the call-to-action groups, usePageHeroImageRecipe() for the media slot, and usePageHeroBackdropRecipe() for the decorative overlay. Each composable creates a fully configured recipe with its own variant axes — the container exposes color, size, orientation, and alignment, while sub-parts inherit a focused subset.

The PageHero recipes integrate directly with the default design tokens preset and generate type-safe utility classes at build time with zero runtime CSS.

Why use the PageHero recipe?

The PageHero recipe helps you:

  • Ship faster with sensible defaults: Get 3 colors, 3 sizes, 2 orientations, and 2 alignments out of the box with a single set of composable calls.
  • Compose layout from real parts: Eight coordinated recipes (container + 7 sub-parts) share the same axes, so your hero stays internally consistent across orientation and alignment changes.
  • Maintain consistency: Compound variants resolve color and orientation-alignment combinations automatically, including dark mode for the neutral color.
  • Customize without forking: Override base styles, default variants, or filter out options you don't need — all through the options API.
  • Stay type-safe: Full TypeScript support means your editor catches invalid color, size, orientation, or alignment values at compile time.
  • Integrate with your tokens: Every value references the design tokens preset, so theme changes propagate automatically.

Usage

Register the recipes

Add the PageHero recipes to a local Styleframe instance. The global styleframe.config.ts provides design tokens and utilities, while the component-level file registers the recipes themselves:

src/components/page-hero.styleframe.ts
import { styleframe } from 'virtual:styleframe';
import {
    usePageHeroRecipe,
    usePageHeroHeadlineRecipe,
    usePageHeroTitleRecipe,
    usePageHeroDescriptionRecipe,
    usePageHeroActionsRecipe,
    usePageHeroLinksRecipe,
    usePageHeroImageRecipe,
    usePageHeroBackdropRecipe,
} from '@styleframe/theme';

const s = styleframe();

const pageHero = usePageHeroRecipe(s);
const pageHeroHeadline = usePageHeroHeadlineRecipe(s);
const pageHeroTitle = usePageHeroTitleRecipe(s);
const pageHeroDescription = usePageHeroDescriptionRecipe(s);
const pageHeroActions = usePageHeroActionsRecipe(s);
const pageHeroLinks = usePageHeroLinksRecipe(s);
const pageHeroImage = usePageHeroImageRecipe(s);
const pageHeroBackdrop = usePageHeroBackdropRecipe(s);

export default s;

Build the component

Import the runtime functions from the virtual module and pass variant props to compute class names. Render the backdrop as the first child so DOM order keeps content above the absolute-positioned overlay:

src/components/PageHero.tsx
import {
    pageHero,
    pageHeroBackdrop,
    pageHeroHeadline,
    pageHeroTitle,
    pageHeroDescription,
    pageHeroActions,
} from "virtual:styleframe";

interface PageHeroProps {
    color?: "light" | "dark" | "neutral";
    size?: "sm" | "md" | "lg";
    orientation?: "vertical" | "horizontal";
    alignment?: "start" | "center";
    title: string;
    description?: string;
    children?: React.ReactNode;
}

export function PageHero({
    color = "neutral",
    size = "md",
    orientation = "vertical",
    alignment = "center",
    title,
    description,
    children,
}: PageHeroProps) {
    return (
        <section className={pageHero({ color, size, orientation, alignment })}>
            <div className={pageHeroBackdrop({ variant: "gradient" })} aria-hidden />
            <div className={pageHeroHeadline({ size, alignment })}>
                <h1 className={pageHeroTitle({ size })}>{title}</h1>
                {description && (
                    <p className={pageHeroDescription({ size })}>{description}</p>
                )}
            </div>
            {children && (
                <div className={pageHeroActions({ size, alignment })}>{children}</div>
            )}
        </section>
    );
}

See it in action

Colors

The PageHero recipe includes 3 color variants: light, dark, and neutral. Like the Card recipe, the PageHero uses neutral-spectrum colors designed for full-bleed content surfaces rather than status communication. Color is applied via compound variants on the root container and inherited by sub-parts through CSS color: inherit.

The neutral color adapts automatically: it uses a light appearance in light mode and a dark appearance in dark mode, making it the safest default for general-purpose hero sections.

Color Reference

ColorTokenUse Case
light@color.whiteLight surfaces, stays light in dark mode
dark@color.gray-900Dark surfaces, stays dark in light mode
neutralAdaptive (light ↔ dark)Default color, adapts to the current color scheme
Pro tip: Use neutral as your default hero color. It adapts automatically to the user's color scheme, so you don't need to manage light and dark variants separately.

Layouts

The hero's layout is controlled by two orthogonal axes: orientation (verticalhorizontal) and alignment (startcenter). Compound variants resolve the four combinations so align-items, justify-items, and text-align always match the chosen layout.

Vertical orientation

Stacks content in a column. The image (if present) sits below the text block.

Horizontal orientation

Splits content into a two-column grid. The image (if present) sits beside the text block.

Start alignment

Aligns text and CTAs to the left edge of the content area. Common for split layouts where the eye reads from text on the left to media on the right.

Center alignment

Centers text and CTAs horizontally. Common for stacked, single-column heroes that anchor the page.

Sizes

Three size variants from sm to lg control the container's vertical padding and gap, plus the headline gap, title font size, description font size, actions gap, links gap and font size, and the image's border radius and shadow. Pass the same size value to every sub-recipe so the proportions stay coherent.

Size Reference

SizeContainer Padding-YContainer GapTitle Font SizeDescription Font SizeImage Radius / Shadow
sm@2@1@font-size.2xl@font-size.sm@border-radius.md / @box-shadow.md
md@3@1.5@font-size.3xl@font-size.md@border-radius.lg / @box-shadow.lg
lg@4@2@font-size.4xl@font-size.lg@border-radius.xl / @box-shadow.xl
Good to know: The size prop must be passed to each sub-recipe individually. The container controls padding-block and overall gap, while every sub-part scales its own typography or spacing.

Anatomy

The PageHero recipe is composed of eight independent recipes that work together to form a complete landing-section layout:

PartRecipeRole
ContainerusePageHeroRecipe()Outer <section> with padding-block, overflow clipping, and position: relative so the backdrop can anchor inside it
HeadlineusePageHeroHeadlineRecipe()<div> wrapper around the title + description; controls vertical stacking and text-align
TitleusePageHeroTitleRecipe()<h1> heading with display-scale typography
DescriptionusePageHeroDescriptionRecipe()<p> lede with clamped 60ch max-width for readable measure
ActionsusePageHeroActionsRecipe()<div> flex row for the primary + secondary CTAs
LinksusePageHeroLinksRecipe()<nav> flex row for secondary text-link CTAs
ImageusePageHeroImageRecipe()<div> media slot with rounded corners and shadow
BackdropusePageHeroBackdropRecipe()<div aria-hidden> decorative overlay positioned absolutely behind content
<!-- All eight parts working together -->
<section class="page-hero(...)">
    <div class="page-hero-backdrop(...)" aria-hidden></div>
    <div class="page-hero-headline(...)">
        <h1 class="page-hero-title(...)">Title</h1>
        <p class="page-hero-description(...)">Description</p>
    </div>
    <div class="page-hero-actions(...)">…CTAs…</div>
    <nav class="page-hero-links(...)">…links…</nav>
    <div class="page-hero-image(...)">…media…</div>
</section>
Pro tip: No dedicated eyebrow recipe is shipped because the existing Badge recipe already covers that use case. Drop a <Badge color="primary" variant="soft" size="sm"> inside the headline above the title for the "What's new" pattern.
Good to know: Render the Backdrop element as the first child of the container. DOM order keeps text and media above the absolute-positioned overlay, so no z-index management is required.

Backdrop

The decorative backdrop has its own variant axis with three styles. Each variant is a self-contained background treatment that adapts to dark mode automatically.

Gradient

A diagonal two-tone linear gradient between the primary and secondary palette tints (light mode) or shades (dark mode).

Pattern

A subtle 16×16 dot grid using radial-gradient. The dots use gray-200 in light mode and gray-800 in dark mode.

Blur

A soft radial blob centered behind the content with a 60px CSS filter: blur(). Uses primary-200 in light mode and primary-800 in dark mode.

Accessibility

  • Use one <h1> per page. The PageHero is intended for the top-of-page introduction. If you render multiple heroes on the same route, demote inner titles to <h2> by overriding the Title sub-component's element.
  • Keep the backdrop decorative. The Backdrop element renders with aria-hidden="true" and pointer-events: none, so screen readers and pointer events skip it. Do not place essential content inside it.
  • Verify contrast ratios. The light and dark colors place fixed-contrast text on fixed backgrounds (WCAG AA 4.5:1 with default tokens). If you customize colors, verify with the WebAIM Contrast Checker.
  • Provide accessible CTAs. The Actions and Links sub-recipes are layout-only wrappers. Wrap interactive content in semantic <a> or <button> elements with descriptive labels.

Customization

Overriding Defaults

Each PageHero composable accepts an optional second argument to override any part of the recipe configuration. Overrides are deep-merged with the defaults, so you only need to specify the properties you want to change:

src/components/page-hero.styleframe.ts
import { styleframe } from 'virtual:styleframe';
import {
    usePageHeroRecipe,
    usePageHeroTitleRecipe,
} from '@styleframe/theme';

const s = styleframe();

const pageHero = usePageHeroRecipe(s, {
    base: {
        paddingTop: '@4',
        paddingBottom: '@4',
    },
    defaultVariants: {
        color: 'neutral',
        size: 'lg',
        orientation: 'horizontal',
        alignment: 'start',
    },
});

const pageHeroTitle = usePageHeroTitleRecipe(s, {
    defaultVariants: {
        size: 'lg',
    },
});

export default s;

Filtering Variants

If you only need a subset of the available variants, use the filter option to limit which values are generated. This reduces the output CSS and keeps your component API focused:

src/components/page-hero.styleframe.ts
import { styleframe } from 'virtual:styleframe';
import { usePageHeroRecipe } from '@styleframe/theme';

const s = styleframe();

// Only generate the neutral color in vertical orientation
const pageHero = usePageHeroRecipe(s, {
    filter: {
        color: ['neutral'],
        orientation: ['vertical'],
    },
});

export default s;
Good to know: Filtering also removes compound variants and adjusts default variants that reference filtered-out values, so your recipe stays consistent.

API Reference

usePageHeroRecipe(s, options?)

Creates the root <section> container recipe with padding-block, overflow clipping, and position: relative for backdrop anchoring.

Parameters:

ParameterTypeDescription
sStyleframeThe Styleframe instance
optionsDeepPartial<RecipeConfig>Optional overrides for the recipe configuration
options.baseVariantDeclarationsBlockCustom base styles for the container
options.variantsVariantsCustom variant definitions
options.defaultVariantsRecord<keyof Variants, string>Default variant values
options.compoundVariantsCompoundVariant[]Custom compound variant definitions
options.filterRecord<string, string[]>Limit which variant values are generated

Variants:

VariantOptionsDefault
colorlight, dark, neutralneutral
sizesm, md, lgmd
orientationvertical, horizontalvertical
alignmentstart, centercenter

usePageHeroHeadlineRecipe(s, options?)

Creates the headline wrapper recipe (a flex column for the title + description).

Variants:

VariantOptionsDefault
sizesm, md, lgmd
alignmentstart, centercenter

usePageHeroTitleRecipe(s, options?)

Creates the title recipe (<h1>) with display-scale typography.

Variants:

VariantOptionsDefault
sizesm, md, lgmd

usePageHeroDescriptionRecipe(s, options?)

Creates the description recipe (<p>) with a clamped 60ch max-width.

Variants:

VariantOptionsDefault
sizesm, md, lgmd

usePageHeroActionsRecipe(s, options?)

Creates the actions container recipe (a flex row that wraps).

Variants:

VariantOptionsDefault
sizesm, md, lgmd
alignmentstart, centercenter

usePageHeroLinksRecipe(s, options?)

Creates the secondary-links container recipe (a <nav> flex row).

Variants:

VariantOptionsDefault
sizesm, md, lgmd
alignmentstart, centercenter

usePageHeroImageRecipe(s, options?)

Creates the image-slot recipe with overflow: hidden, scaled border-radius, and box-shadow.

Variants:

VariantOptionsDefault
sizesm, md, lgmd

usePageHeroBackdropRecipe(s, options?)

Creates the decorative backdrop recipe (position: absolute; inset: 0). The variant axis switches between gradient, pattern, and blur background treatments.

Variants:

VariantOptionsDefault
variantgradient, pattern, blurgradient

Learn more about recipes →

Best Practices

  • Pass size to every sub-recipe: The container controls padding-block, but every sub-part scales its own typography or spacing. Pass the same size value through to keep proportions consistent.
  • Pass alignment to Headline, Actions, and Links: These are the three sub-recipes whose layout flips with alignment. The container's alignment only affects its own align-items / justify-items.
  • Render the Backdrop first: DOM order is what stacks content above the absolute-positioned overlay. Putting the Backdrop last hides everything else.
  • Use neutral for general-purpose heroes: The neutral color adapts to light and dark mode automatically.
  • Reach for horizontal orientation when you have media: Without an image or screenshot to fill the second column, the horizontal layout wastes space. Use vertical for text-only heroes.
  • Filter what you don't need: If your component only uses one orientation, pass a filter option to reduce generated CSS.
  • Override defaults at the recipe level: Set your most common combination as defaultVariants so component consumers write less code.

FAQ