Page Hero
alphav0.2.0Full-bleed gradient hero band — badge, title, highlight, description, stats row, and a reveal-on-mount animation.
Context
Top-of-page hero band for landing pages, blog indexes, marketing sections, and app welcomes. Three density levels (compact / default / full) cover full-screen splashes down to sub-page banners. Engine is generic; news-flavored defaults live in dummy-data + demo (kasder NewsHero's content). Composable `children` slot supports stats / CTAs / search / anything. Migration origin: kasder kas-social-front-v0 commons/PageHero.tsx (engine) + sections/news/NewsHero.tsx (news-flavored shell). Drops framer-motion in favor of the existing pro-ui CSS keyframe — saves ~100KB peer dep + inherits prefers-reduced-motion respect for free.
Installation
pnpm dlx shadcn@latest initpnpm dlx shadcn@latest add @ilinxa/page-heroAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/page-hero-fixturesCLI can't resolve @ilinxa? The namespace is listed in the official shadcn registry directory, so current CLIs need no configuration. If yours can't resolve it (older or pinned versions, self-hosted mirrors), register it manually in components.json:
"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}Preview
Latest StoriesFrom Our Team
Insights, announcements, and behind-the-scenes from our editorial team.
Demo source
"use client"; import { Tabs, TabsContent, TabsTrigger } from "@/components/ui/tabs";import { SwipeTabsList } from "@/components/site/swipe-tabs-list";import { Button } from "@/components/ui/button";import { HeroStats, PageHero } from "./page-hero";import { NEWS_HERO_DEFAULTS, NEWS_HERO_DEFAULTS_TR, NEWS_HERO_STATS_EN, NEWS_HERO_STATS_TR,} from "./dummy-data"; export default function PageHeroDemo() { return ( <Tabs defaultValue="default" className="w-full"> <SwipeTabsList> <TabsTrigger value="default">Default</TabsTrigger> <TabsTrigger value="stats">With HeroStats</TabsTrigger> <TabsTrigger value="cta">CTA cluster</TabsTrigger> <TabsTrigger value="density">Density</TabsTrigger> <TabsTrigger value="i18n">Localized</TabsTrigger> </SwipeTabsList> <TabsContent value="default" className="mt-6 overflow-hidden rounded-2xl border border-border/50"> <PageHero badge={NEWS_HERO_DEFAULTS.badge} badgeIcon={NEWS_HERO_DEFAULTS.badgeIcon} title={NEWS_HERO_DEFAULTS.title} titleHighlight={NEWS_HERO_DEFAULTS.titleHighlight} description={NEWS_HERO_DEFAULTS.description} /> </TabsContent> <TabsContent value="stats" className="mt-6 overflow-hidden rounded-2xl border border-border/50"> <PageHero badge={NEWS_HERO_DEFAULTS.badge} badgeIcon={NEWS_HERO_DEFAULTS.badgeIcon} title={NEWS_HERO_DEFAULTS.title} titleHighlight={NEWS_HERO_DEFAULTS.titleHighlight} description={NEWS_HERO_DEFAULTS.description} > <HeroStats stats={NEWS_HERO_STATS_EN} /> </PageHero> </TabsContent> <TabsContent value="cta" className="mt-6 overflow-hidden rounded-2xl border border-border/50"> <PageHero badge="New: v2.0" title="Build faster" titleHighlight="Ship sooner" description="The component library for teams that move." > <div className="flex flex-wrap justify-center gap-4"> <Button size="lg" variant="secondary"> Get started </Button> <Button size="lg" variant="outline" className="bg-transparent text-primary-foreground border-primary-foreground/40 hover:bg-primary-foreground/10 hover:text-primary-foreground"> View docs </Button> </div> </PageHero> </TabsContent> <TabsContent value="density" className="mt-6 space-y-4"> <p className="text-sm text-muted-foreground"> Three density levels — <code>compact</code> (40vh), <code>default</code> (70vh), <code>full</code> (100vh). Below: compact only (full-screen variants would dominate the demo). </p> <div className="overflow-hidden rounded-2xl border border-border/50"> <PageHero density="compact" headingAs="h2" title="Pricing" description="Simple plans for teams of any size." /> </div> </TabsContent> <TabsContent value="i18n" className="mt-6 overflow-hidden rounded-2xl border border-border/50"> <PageHero badge={NEWS_HERO_DEFAULTS_TR.badge} badgeIcon={NEWS_HERO_DEFAULTS_TR.badgeIcon} title={NEWS_HERO_DEFAULTS_TR.title} titleHighlight={NEWS_HERO_DEFAULTS_TR.titleHighlight} description={NEWS_HERO_DEFAULTS_TR.description} > <HeroStats stats={NEWS_HERO_STATS_TR} /> </PageHero> </TabsContent> </Tabs> );} Usage
When to use
Reach for PageHero as the top-of-page hero band on any landing or section: news landing, blog index, marketing page, docs site, app welcome screen. Composes a gradient background, accent badge chip, large title with optional highlight subline, description, and a flexible content slot.
Minimal example
import { PageHero } from "@/components/page-hero";
<PageHero
badge="News & Updates"
title="Latest Stories"
titleHighlight="From Our Team"
description="Insights and announcements."
/>;With HeroStats
The HeroStats sub-component renders the typical icon-circle + bold value + small label triplet, designed for the white-on-gradient contrast inside the hero.
import { HeroStats, PageHero } from "@/components/page-hero";
import { Newspaper, TrendingUp, Clock } from "lucide-react";
<PageHero badge="News" title="Stories" description="Stay informed.">
<HeroStats stats={[
{ icon: Newspaper, value: "500+", label: "Articles" },
{ icon: TrendingUp, value: "10K+", label: "Readers" },
{ icon: Clock, value: "Daily", label: "Updates" },
]} />
</PageHero>;CTA cluster
<PageHero
badge="New: v2.0"
title="Build faster"
titleHighlight="Ship sooner"
>
<div className="flex justify-center gap-4">
<Button size="lg" variant="secondary">Get started</Button>
<Button size="lg" variant="outline">View docs</Button>
</div>
</PageHero>;Density variations
Three options via the density prop:
compact—min-h-[40vh]. Sub-page heroes, section dividers.default—min-h-[70vh]. Most landing pages.full—min-h-screen. Splash / welcome screens.
Custom title rendering
For mixed-color or italic-word titles beyond the simple title + titleHighlight combo, pass titleSlot:
<PageHero
title="" // unused when titleSlot is provided
titleSlot={
<h1 className="text-5xl font-bold text-primary-foreground">
The <em className="italic text-primary-foreground">future</em> of news
</h1>
}
description="..."
/>;Animation
Reveal animation runs on mount via the project-wide reveal-up CSS keyframe with 60ms staggered delays (badge → title → description → children). Respects prefers-reduced-motion automatically (free via the existing keyframe). Disable entirely with disableReveal={true}.
Accessibility
- Section is a landmark with
aria-labelledbypointing to the title id (auto-generated viauseId). - Badge icon is
aria-hidden; badge text is the meaningful label. - Heading semantic level configurable via
headingAs={"h1" | "h2" | "h3"}. Defaulth1. - Reduced-motion users see content instantly (no transitions).
Features
- Full-bleed gradient hero band — 3 density levels (compact 40vh / default 70vh / full 100vh)
- 4-tier reveal stagger via project's `reveal-up` keyframe — badge / title / description / children, 60ms apart
- Drops framer-motion peer dep (~100KB) — uses existing pro-ui CSS keyframe
- `prefers-reduced-motion` respect inherited from the keyframe — content fades instantly for reduced-motion users
- Badge chip (rounded-full, accent tint) with optional Lucide-style icon
- Title-with-highlight pattern — second `<span>` rendered as a new line in accent color
- `titleSlot` prop accepts ReactNode for rich custom rendering
- `children` slot for stats / CTAs / search / breadcrumbs / anything
- `HeroStats` sub-component exported separately for the icon-circle + value + label triplet pattern
- Bottom SVG wave that fills with `--background` so the hero transitions cleanly
- Subtle SVG noise pattern overlay on the gradient
- ARIA — `aria-labelledby` wires section landmark to title id (useId)
- Heading semantic level configurable via `headingAs` (h1 | h2 | h3)
- `disableReveal` prop for SSR-only contexts
- React.memo wrapped