Skip to content
Motion
FreeIn reviewv1.0.0

Page Intro

The compact first section of an inner page: its one h1, a line and its actions, plain or on a tinted band.

npx shadcn@latest add @vibebox/hero-page-intro

Controls

Variant
<HeroPageIntro />

About

A new block for Launchpad's inner pages, replacing the site kit's page hero. It keeps the full hero base, so an inner page can be swapped to a full hero without losing copy.

Use it when

  • The first section of any page except the home page.

Skip it when

  • The home page: use a full hero.

Install

  1. 1Add the VibeBox registry to components.json:
    {
      "registries": {
        "@vibebox": "https://motion.vibeboxph.com/r/{name}.json"
      }
    }
  2. 2Add the block with the shadcn CLI:
    npx shadcn@latest add @vibebox/hero-page-intro
  3. 3Use it:
    import { HeroPageIntro } from "@/components/vibebox/hero-page-intro";
    import { home } from "@/content/pages/home";
    
    <HeroPageIntro content={home.intro} variant="plain" id="top" />

Code

2 files
components/vibebox/hero-page-intro/index.tsx
/**
 * VibeBox Motion · Page Intro
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
import { Actions, Container, Eyebrow, headingId, Media, Section, SlotText, type BlockProps } from "@/lib/vibebox/block-kit";
import { cn } from "@/lib/utils";
import type { Content, Variant } from "./schema";

/*
 * The first section of an inner page (Services, About, Pricing): the page's one h1, a line, and
 * its actions, compact so the page's content starts high. `band` sets it on a tinted panel.
 */

export type HeroPageIntroProps = BlockProps<Content, Variant>;

export function HeroPageIntro({ content, variant = "plain", id, tone, className }: HeroPageIntroProps) {
  const band = variant === "band";
  return (
    <Section slug="hero-page-intro" variant={variant} id={id} tone={tone} labelledBy={headingId(id)} spacing={false} className={cn("pt-[clamp(40px,6vw,88px)] pb-[clamp(24px,4vw,56px)]", className)}>
      <Container>
        <div
          className={cn(
            "grid grid-cols-[minmax(0,1fr)] items-end gap-8",
            content.media && "md:grid-cols-[minmax(0,1fr)_minmax(0,0.42fr)]",
            band && "rounded-xl bg-[linear-gradient(140deg,color-mix(in_oklab,var(--spectrum-2)_10%,var(--card)),var(--card))] p-6 ring-1 ring-border sm:p-10 lg:p-12",
          )}
        >
          <div className="grid gap-5">
            <Eyebrow value={content.eyebrow} />
            <SlotText as="h1" value={content.heading} path="heading" id={headingId(id)} reveal className="font-display text-[clamp(2.4rem,1.4rem+3.6vw,4.5rem)] leading-[1.02] font-semibold tracking-[-0.035em] text-balance" />
            <SlotText as="p" value={content.lead} path="lead" className="max-w-[60ch] text-[clamp(1.02rem,1.2vw,1.15rem)] leading-relaxed text-pretty text-muted-foreground" />
            <Actions primary={content.primaryAction} secondary={content.secondaryAction} className="mt-1" />
          </div>
          {content.media && (
            <div className="hidden aspect-[4/3] overflow-hidden rounded-xl ring-1 ring-border md:block">
              <Media image={content.media} priority sizes="30vw" />
            </div>
          )}
        </div>
      </Container>
    </Section>
  );
}
components/vibebox/hero-page-intro/schema.ts
/**
 * VibeBox Motion · Page Intro
 * Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
 * https://motion.vibeboxph.com/license
 * Use it in unlimited personal, commercial and client projects.
 * Do not sell or redistribute it as a component, kit, template or builder.
 */
import { sections, type z } from "@/lib/vibebox/content";

export const variants = ["plain", "band"] as const;

export type Variant = (typeof variants)[number];

/* The hero base, kept whole so an inner page can swap to a full hero; the intro shows its media small. */
export const content = sections.hero;

export type Content = z.infer<typeof content>;

Content

Typed slots from schema.ts. Facts (hours, prices, quotes, names) come only from the business; until then they render as fill-ins.

SlotLabelTypeLimitFact
eyebrowKickertext32 chars—
headingHeadlinetext80 chars—
leadIntrotext160 chars—
primaryActionMain buttonaction24 chars—
secondaryActionSecond buttonaction24 chars—
mediaImageimage——

Variants

  • Plaindefault

    plain

    The heading on the page surface.

  • Band

    band

    The heading on a softly tinted panel.

Accessibility and motion

Keyboard
The actions.
Screen readers
The page's one h1, its line and actions.
Touch
Nothing depends on hover.
Reduced motion
Nothing moves.

Edge cases and limits

  • An 80-character heading wraps to three lines on phones.
  • A headline about moving better beside a mosaic of a tile scene, a bloom scene and a class list

    The promise beside a three-tile mosaic: a tall photo, a second image and a card of what the business offers.

    Heroes block · Server

  • A centred headline with two buttons and highlight chips above a wide image of stacked cards

    One centred promise with its actions and highlights, the image below, on a plain, haloed or gradient panel ground.

    Heroes block · Server

  • A large serif headline about bread beside a framed window scene with a caption fill-in

    A magazine opening: a large serif headline, the intro and actions, tags, and the photo framed tall or set as a band.

    Heroes block · Server

↑ ↓ to move↵ to openesc to close