ComponentsHeroTextAnimation
HeroTextAnimation
Reveal a headline one word or line at a time.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import {
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
export function Example() {
return (
<HeroTextAnimationProvider>
<HeroTextAnimation text="Build production-ready landing pages faster." />
</HeroTextAnimationProvider>
);
}Each animation is shown in a distinct, production-style hero tuned to where it fits best — SaaS, developer tools, security, editorial, luxury, and more. Expand a source panel when you need the implementation.
For liquid, prismatic, metallic, and mouse-following particle lettering, explore ShaderHeroText.
SaaS product hero — staggered words
The default staggered word reveal in a conversion-focused SaaS layout: navbar, dual CTAs, and an icon-led proof grid.
Turn your launch headline into a clear first impression.
Pair the animated headline with plain-language support copy, clear actions, and icon-led proof points so the hero stays usable before and after the motion completes.
- Readable firstThe final headline stays present in server-rendered HTML.
- Motion awareReduced-motion visitors get a calm static or opacity-only state.
- Fast by defaultShort stagger timing keeps the hero clear without delaying action.
- Easy to scanIcon and text pairs turn hero promises into quick proof points.
Show sourceexamples/hero-text-animation/production-hero.tsx
"use client";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemDescription,
DropdownMenuItemIcon,
DropdownMenuItemLabel,
DropdownMenuLabel,
DropdownMenuSection,
DropdownMenuSeparator,
DropdownMenuTrigger,
HeroTextAnimation,
HeroTextAnimationProvider,
RevealButton,
} from "@dethink/components";
import {
ArrowRight,
BookOpenText,
ChevronDown,
Eye,
Gauge,
Layers3,
LifeBuoy,
PlayCircle,
Rocket,
ShieldCheck,
Sparkles,
} from "lucide-react";
const navItems = [
{ href: "#examples", label: "Examples" },
{ href: "#installation", label: "Install" },
{ href: "#props", label: "API" },
];
const resourceItems = [
{
icon: Layers3,
label: "Animation patterns",
description: "Choose a safe motion style for each hero message.",
},
{
icon: ShieldCheck,
label: "Accessibility model",
description: "Keep one readable heading while fragments animate visually.",
},
{
icon: LifeBuoy,
label: "Implementation notes",
description: "Reduced-motion, SSR, and registry guidance in one place.",
},
];
const heroSignals = [
{
icon: Eye,
title: "Readable first",
description: "The final headline stays present in server-rendered HTML.",
},
{
icon: ShieldCheck,
title: "Motion aware",
description:
"Reduced-motion visitors get a calm static or opacity-only state.",
},
{
icon: Gauge,
title: "Fast by default",
description:
"Short stagger timing keeps the hero clear without delaying action.",
},
{
icon: BookOpenText,
title: "Easy to scan",
description:
"Icon and text pairs turn hero promises into quick proof points.",
},
];
export function HeroTextAnimationProductionHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-production-heading"
className="bg-background text-foreground border-border rounded-md border"
>
<div className="mx-auto max-w-5xl px-5 py-5 sm:px-8 lg:px-10">
<header className="border-border bg-background/95 flex min-h-14 items-center gap-4 rounded-md border px-3 shadow-sm">
<a
href="#examples"
aria-label="Dethink hero animation examples"
className="focus-visible:ring-ring focus-visible:ring-offset-background flex min-w-0 shrink-0 items-center gap-2 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
<span
aria-hidden="true"
className="bg-primary text-primary-foreground grid size-8 shrink-0 place-items-center rounded-md"
>
<Sparkles className="size-4" />
</span>
<span className="font-heading hidden text-sm font-semibold sm:inline">
Dethink Hero
</span>
</a>
<nav
aria-label="Hero recipe navigation"
className="hidden min-w-0 flex-1 md:block"
>
<ul className="flex items-center justify-center gap-1">
{navItems.map((item) => (
<li key={item.href}>
<a
href={item.href}
className="text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background inline-flex h-9 items-center rounded-md px-3 text-sm font-medium transition-colors outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
{item.label}
</a>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex shrink-0 items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger
size="sm"
variant="ghost"
className="gap-1.5"
>
Resources
<ChevronDown aria-hidden="true" className="size-3.5" />
</DropdownMenuTrigger>
<DropdownMenuContent placement="bottom end" showArrow>
<DropdownMenuSection>
<DropdownMenuLabel>Hero resources</DropdownMenuLabel>
{resourceItems.map(({ icon: Icon, label, description }) => (
<DropdownMenuItem key={label} textValue={label}>
<DropdownMenuItemIcon aria-hidden="true">
<Icon />
</DropdownMenuItemIcon>
<DropdownMenuItemLabel>{label}</DropdownMenuItemLabel>
<DropdownMenuItemDescription>
{description}
</DropdownMenuItemDescription>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem textValue="Open production checklist">
<DropdownMenuItemIcon aria-hidden="true">
<BookOpenText />
</DropdownMenuItemIcon>
<DropdownMenuItemLabel>
Production checklist
</DropdownMenuItemLabel>
</DropdownMenuItem>
</DropdownMenuSection>
</DropdownMenuContent>
</DropdownMenu>
<RevealButton
className="hidden sm:inline-flex"
icon={<PlayCircle />}
label="Preview"
size="sm"
variant="outline"
/>
<RevealButton
icon={<Rocket />}
label="Start"
size="sm"
variant="solid"
/>
</div>
</header>
</div>
<div className="mx-auto grid max-w-5xl gap-8 px-5 pt-6 pb-10 sm:px-8 lg:grid-cols-[minmax(0,1fr)_22rem] lg:items-center lg:px-10 lg:pt-10 lg:pb-14">
<div className="min-w-0 space-y-7">
<div className="border-border bg-muted/50 text-muted-foreground inline-flex max-w-full items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium">
<Sparkles
aria-hidden="true"
className="text-primary size-3.5 shrink-0"
/>
<span className="truncate">Hero section recipe</span>
</div>
<div className="space-y-5">
<HeroTextAnimation
id="hero-text-production-heading"
repeat
repeatDelay={1.6}
text="Turn your launch headline into a clear first impression."
className="font-heading text-foreground max-w-3xl text-4xl leading-[1.03] font-semibold tracking-normal sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground max-w-xl text-base leading-7 sm:text-lg">
Pair the animated headline with plain-language support copy,
clear actions, and icon-led proof points so the hero stays
usable before and after the motion completes.
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row">
<Button asChild size="lg" rightIcon={<ArrowRight />}>
<a href="#installation">Install component</a>
</Button>
<Button asChild size="lg" variant="outline">
<a href="#props">Review props</a>
</Button>
</div>
</div>
<ul className="grid min-w-0 gap-3 sm:grid-cols-2 lg:grid-cols-1">
{heroSignals.map(({ icon: Icon, title, description }) => (
<li
key={title}
className="border-border bg-muted/35 flex min-w-0 gap-3 rounded-md border p-4"
>
<span
aria-hidden="true"
className="bg-primary/10 text-primary grid size-10 shrink-0 place-items-center rounded-md"
>
<Icon className="size-4" />
</span>
<span className="min-w-0">
<span className="block text-sm font-semibold">{title}</span>
<span className="text-muted-foreground mt-1 block text-sm leading-6">
{description}
</span>
</span>
</li>
))}
</ul>
</div>
</section>
</HeroTextAnimationProvider>
);
}Design studio hero — masked curtain
An editorial agency layout where lines rise inside overflow masks: restrained typography, thin rules, and a numbered discipline index.
Independent design & motion studio
We design brands that move with intent.
A small studio for founders who care about the details. We shape identity, product, and motion into one deliberate, unhurried whole.
- 01
Brand systems
Identity, voice, and design language built to scale.
- 02
Web & product
Editorial marketing sites and considered product UI.
- 03
Motion & film
Title sequences, launch films, and interface motion.
Show sourceexamples/hero-text-animation/masked-curtain-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowUpRight } from "lucide-react";
const navItems = ["Work", "Studio", "Journal", "Contact"];
const disciplines = [
{
index: "01",
title: "Brand systems",
description: "Identity, voice, and design language built to scale.",
},
{
index: "02",
title: "Web & product",
description: "Editorial marketing sites and considered product UI.",
},
{
index: "03",
title: "Motion & film",
description: "Title sequences, launch films, and interface motion.",
},
];
export function HeroTextAnimationMaskedCurtainHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-masked-curtain-heading"
className="bg-background text-foreground border-border overflow-hidden rounded-md border"
>
<header className="border-border flex items-center justify-between gap-4 border-b px-5 py-4 sm:px-8 lg:px-12">
<a
href="#examples"
className="font-heading focus-visible:ring-ring focus-visible:ring-offset-background rounded-sm text-xs font-semibold tracking-[0.28em] uppercase outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
Studio Dethink
</a>
<nav
aria-label="Studio navigation"
className="hidden items-center gap-7 md:flex"
>
{navItems.map((item) => (
<a
key={item}
href="#examples"
className="text-muted-foreground hover:text-foreground text-xs font-medium tracking-[0.14em] uppercase transition-colors"
>
{item}
</a>
))}
</nav>
<span className="text-muted-foreground hidden text-xs tracking-[0.2em] uppercase sm:inline">
Est. MMXXVI
</span>
</header>
<div className="mx-auto max-w-3xl px-5 py-16 text-center sm:px-8 lg:py-24">
<p className="text-muted-foreground text-xs font-medium tracking-[0.24em] uppercase">
Independent design & motion studio
</p>
<span
aria-hidden="true"
className="bg-border mx-auto mt-6 block h-px w-10"
/>
<HeroTextAnimation
animation="masked-curtain"
ariaLabel="We design brands that move with intent."
id="hero-text-masked-curtain-heading"
repeat
repeatDelay={1.8}
text={"We design brands\nthat move with intent."}
className="font-heading text-foreground mx-auto mt-8 max-w-3xl text-4xl leading-[1.05] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mx-auto mt-7 max-w-xl text-base leading-7 sm:text-lg">
A small studio for founders who care about the details. We shape
identity, product, and motion into one deliberate, unhurried whole.
</p>
<div className="mt-9 flex flex-col items-center justify-center gap-4 sm:flex-row">
<Button asChild size="lg" variant="outline">
<a href="#installation">Start a project</a>
</Button>
<a
href="#examples"
className="text-foreground focus-visible:ring-ring focus-visible:ring-offset-background group inline-flex items-center gap-1.5 rounded-sm text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
View selected work
<ArrowUpRight
aria-hidden="true"
className="size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
/>
</a>
</div>
</div>
<ul className="border-border grid border-t sm:grid-cols-3">
{disciplines.map(({ index, title, description }, position) => (
<li
key={title}
className={`px-5 py-8 sm:px-8 lg:px-12 ${
position > 0
? "border-border border-t sm:border-t-0 sm:border-l"
: ""
}`}
>
<span className="text-muted-foreground font-mono text-xs tracking-widest">
{index}
</span>
<p className="font-heading mt-3 text-lg font-semibold">{title}</p>
<p className="text-muted-foreground mt-1.5 text-sm leading-6">
{description}
</p>
</li>
))}
</ul>
</section>
</HeroTextAnimationProvider>
);
}Developer tool hero — typewriter
A CLI/DX layout pairing the typed headline with a live terminal window, install command, and GitHub actions.
Ship your CLI in an afternoon.
A typed, developer-first toolkit for building command-line tools your team actually enjoys. Zero config, fully tree-shakeable.
$ npm i -D @dethink/clinpx dethink init landing
◇ Scaffolding hero, motion tokens, registry…
◇ Wiring reduced-motion + SSR fallbacks…
✓ Ready in 1.2s — run pnpm dev
Show sourceexamples/hero-text-animation/typewriter-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import {
ArrowRight,
BookOpen,
Check,
Copy,
GitBranch,
Star,
Terminal,
} from "lucide-react";
const terminalLines = [
{ prompt: true, text: "npx dethink init landing" },
{ prompt: false, text: "◇ Scaffolding hero, motion tokens, registry…" },
{ prompt: false, text: "◇ Wiring reduced-motion + SSR fallbacks…" },
{ prompt: false, text: "✓ Ready in 1.2s — run pnpm dev", ok: true },
];
export function HeroTextAnimationTypewriterHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-typewriter-heading"
className="bg-background text-foreground border-border overflow-hidden rounded-md border"
>
<div className="mx-auto grid max-w-5xl items-center gap-10 px-5 py-12 sm:px-8 lg:grid-cols-[minmax(0,1fr)_26rem] lg:px-10 lg:py-16">
<div className="min-w-0">
<span className="border-border bg-muted/50 text-muted-foreground inline-flex items-center gap-2 rounded-full border px-3 py-1 font-mono text-xs font-medium">
<span
aria-hidden="true"
className="bg-success size-1.5 rounded-full"
/>
v2.0.0 — now stable
</span>
<HeroTextAnimation
animation="typewriter"
duration={1.9}
id="hero-text-typewriter-heading"
repeat
repeatDelay={2.2}
text="Ship your CLI in an afternoon."
className="font-heading text-foreground mt-6 max-w-xl text-4xl leading-[1.05] font-semibold tracking-tight sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mt-6 max-w-md text-base leading-7 sm:text-lg">
A typed, developer-first toolkit for building command-line tools
your team actually enjoys. Zero config, fully tree-shakeable.
</p>
<div className="border-border bg-muted/40 mt-7 flex max-w-md items-center gap-3 rounded-md border px-3.5 py-2.5">
<Terminal
aria-hidden="true"
className="text-muted-foreground size-4 shrink-0"
/>
<code className="text-foreground min-w-0 flex-1 truncate font-mono text-sm">
<span className="text-muted-foreground select-none">$ </span>
npm i -D @dethink/cli
</code>
<button
type="button"
aria-label="Copy install command"
className="text-muted-foreground hover:text-foreground hover:bg-background focus-visible:ring-ring focus-visible:ring-offset-background grid size-7 shrink-0 place-items-center rounded outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
<Copy aria-hidden="true" className="size-3.5" />
</button>
</div>
<div className="mt-7 flex flex-col gap-3 sm:flex-row sm:items-center">
<Button asChild size="lg" leftIcon={<BookOpen />}>
<a href="#installation">Read the docs</a>
</Button>
<Button
asChild
size="lg"
variant="ghost"
leftIcon={<GitBranch />}
rightIcon={
<span className="text-muted-foreground inline-flex items-center gap-1 text-xs">
<Star aria-hidden="true" className="size-3.5" />
12.4k
</span>
}
>
<a href="#examples">Star on GitHub</a>
</Button>
</div>
</div>
<div className="bg-foreground text-background min-w-0 overflow-hidden rounded-lg shadow-lg">
<div className="border-background/15 flex items-center gap-2 border-b px-4 py-3">
<span className="flex items-center gap-1.5" aria-hidden="true">
<span className="bg-destructive size-3 rounded-full" />
<span className="bg-warning size-3 rounded-full" />
<span className="bg-success size-3 rounded-full" />
</span>
<span className="text-background/60 ml-2 font-mono text-xs">
~/app — zsh
</span>
</div>
<div className="space-y-2 px-4 py-5 font-mono text-[13px] leading-relaxed">
{terminalLines.map((line, index) => (
<p
key={index}
className={
line.ok
? "text-background flex items-start gap-2"
: line.prompt
? "text-background flex items-start gap-2"
: "text-background/65 flex items-start gap-2"
}
>
{line.prompt ? (
<span aria-hidden="true" className="text-background/50">
$
</span>
) : line.ok ? (
<Check
aria-hidden="true"
className="text-success mt-0.5 size-3.5 shrink-0"
/>
) : (
<span aria-hidden="true" className="text-background/30">
›
</span>
)}
<span className="min-w-0">{line.text}</span>
</p>
))}
<p className="text-background flex items-center gap-2 pt-1 font-mono">
<span aria-hidden="true" className="text-background/50">
$
</span>
<span
aria-hidden="true"
className="bg-background inline-block h-4 w-2 animate-pulse motion-reduce:animate-none"
/>
</p>
</div>
<div className="border-background/15 text-background/60 flex items-center justify-between border-t px-4 py-2.5 font-mono text-xs">
<span>node v22.4.0</span>
<span className="inline-flex items-center gap-1.5">
Done
<ArrowRight aria-hidden="true" className="size-3" />
</span>
</div>
</div>
</div>
</section>
</HeroTextAnimationProvider>
);
}Security platform hero — scramble decrypt
A zero-trust layout with a grid backdrop, compliance chips, and a live encrypted-status console beside the decrypting headline.
Decrypt threats before they move.
Runtime detection that resolves noise into signal. Continuous posture, automated key rotation, and audit trails your security team can actually read.
- handshake — TLS 1.3 established
- identity — device attested
- keys — rotated 4m ago
- secret — sk_live_••••••••••••4e2a
Show sourceexamples/hero-text-animation/scramble-decrypt-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import type { CSSProperties } from "react";
import {
ArrowRight,
Fingerprint,
KeyRound,
Lock,
ShieldCheck,
} from "lucide-react";
const gridStyle: CSSProperties = {
backgroundImage:
"linear-gradient(var(--dt-color-border) 1px, transparent 1px), linear-gradient(90deg, var(--dt-color-border) 1px, transparent 1px)",
backgroundSize: "2.75rem 2.75rem",
maskImage: "radial-gradient(120% 90% at 30% 0%, black, transparent 75%)",
WebkitMaskImage:
"radial-gradient(120% 90% at 30% 0%, black, transparent 75%)",
};
const certifications = [
{ icon: ShieldCheck, label: "SOC 2 Type II" },
{ icon: Lock, label: "ISO 27001" },
{ icon: Fingerprint, label: "Zero-trust" },
];
const logLines = [
{ tone: "info", text: "handshake — TLS 1.3 established" },
{ tone: "info", text: "identity — device attested" },
{ tone: "ok", text: "keys — rotated 4m ago" },
{ tone: "muted", text: "secret — sk_live_••••••••••••4e2a" },
];
export function HeroTextAnimationScrambleDecryptHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-scramble-heading"
className="bg-background text-foreground border-border relative overflow-hidden rounded-md border"
>
<span
aria-hidden="true"
style={gridStyle}
className="pointer-events-none absolute inset-0 opacity-60"
/>
<div className="relative mx-auto grid max-w-5xl items-center gap-10 px-5 py-14 sm:px-8 lg:grid-cols-[minmax(0,1fr)_22rem] lg:px-10 lg:py-20">
<div className="min-w-0">
<span className="border-border bg-background/70 text-muted-foreground inline-flex items-center gap-2 rounded-full border px-3 py-1 font-mono text-xs backdrop-blur">
<KeyRound aria-hidden="true" className="text-info size-3.5" />
End-to-end encrypted by default
</span>
<HeroTextAnimation
animation="scramble-decrypt"
duration={1}
id="hero-text-scramble-heading"
repeat
repeatDelay={1.8}
text="Decrypt threats before they move."
className="font-heading text-foreground mt-6 max-w-2xl text-4xl leading-[1.05] font-semibold tracking-tight sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mt-6 max-w-lg text-base leading-7 sm:text-lg">
Runtime detection that resolves noise into signal. Continuous
posture, automated key rotation, and audit trails your security
team can actually read.
</p>
<div className="mt-7 flex flex-wrap items-center gap-2.5">
{certifications.map(({ icon: Icon, label }) => (
<span
key={label}
className="border-border bg-background/70 text-foreground inline-flex items-center gap-2 rounded-md border px-3 py-1.5 font-mono text-xs font-medium"
>
<Icon
aria-hidden="true"
className="text-muted-foreground size-3.5"
/>
{label}
</span>
))}
</div>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<Button asChild size="lg" rightIcon={<ArrowRight />}>
<a href="#installation">Request access</a>
</Button>
<Button asChild size="lg" variant="outline">
<a href="#props">Read the security model</a>
</Button>
</div>
</div>
<div className="border-border bg-background/80 min-w-0 rounded-lg border shadow-sm backdrop-blur">
<div className="border-border flex items-center justify-between border-b px-4 py-3">
<span className="inline-flex items-center gap-2 font-mono text-xs font-medium">
<span className="relative flex size-2">
<span className="bg-success/60 absolute inline-flex size-full animate-ping rounded-full motion-reduce:hidden" />
<span className="bg-success relative inline-flex size-2 rounded-full" />
</span>
All systems encrypted
</span>
<span className="text-muted-foreground font-mono text-xs">
live
</span>
</div>
<ul className="space-y-3 px-4 py-4 font-mono text-xs">
{logLines.map(({ tone, text }, index) => (
<li key={index} className="flex items-center gap-2.5">
<span
aria-hidden="true"
className={`size-1.5 shrink-0 rounded-full ${
tone === "ok"
? "bg-success"
: tone === "info"
? "bg-info"
: "bg-muted-foreground/50"
}`}
/>
<span
className={
tone === "muted"
? "text-muted-foreground truncate"
: "text-foreground truncate"
}
>
{text}
</span>
</li>
))}
</ul>
<div className="border-border text-muted-foreground border-t px-4 py-2.5 font-mono text-[11px]">
audit log · 2,481 events today
</div>
</div>
</div>
</section>
</HeroTextAnimationProvider>
);
}Multi-audience hero — rotating keyword
A product-led layout that swaps the audience keyword while keeping one stable accessible sentence, audience chips, and a team avatar cloud.
Launch pages for startups, agencies, product teams, and founders.
One flexible page builder that speaks every team's language. Ship a launch that fits the audience — without a redesign for each one.
Trusted across 12 departments at 4,000+ companies
Show sourceexamples/hero-text-animation/rotating-keyword-hero.tsx
"use client";
import { useEffect, useState } from "react";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowRight, Play } from "lucide-react";
const audiences = ["Startups", "Agencies", "Product teams", "Founders"];
// The trailing period is baked into each keyword (rather than a separate
// rotatingKeywordSuffix) so it rides the keyword's own text baseline instead of
// the overflow-clipped rotating slot's synthesized baseline, which otherwise
// dropped the "." below the line with an odd gap.
const rotatingKeywords = [
"startups.",
"agencies.",
"product teams.",
"founders.",
];
const rotateIntervalMs = 2000;
const teamAvatars = [
{ initials: "SO", tone: "bg-primary/15 text-primary" },
{ initials: "CS", tone: "bg-info/15 text-info" },
{ initials: "FL", tone: "bg-success/15 text-success" },
{ initials: "PM", tone: "bg-warning/20 text-warning" },
{ initials: "OP", tone: "bg-muted text-muted-foreground" },
];
export function HeroTextAnimationRotatingKeywordHero() {
const [keywordIndex, setKeywordIndex] = useState(0);
// Loop the keyword indefinitely for the showcase. The component's built-in
// autoRotateKeywords stops after ~5s (a WCAG 2.2.2 guard against text that
// moves forever), so we drive the index ourselves in controlled mode to keep
// the demo playing — while still honoring prefers-reduced-motion.
useEffect(() => {
if (typeof window === "undefined") {
return;
}
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
return;
}
const intervalId = window.setInterval(() => {
setKeywordIndex((index) => (index + 1) % rotatingKeywords.length);
}, rotateIntervalMs);
return () => window.clearInterval(intervalId);
}, []);
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-rotating-keyword-heading"
className="bg-background text-foreground border-border overflow-hidden rounded-md border"
>
<div className="mx-auto max-w-3xl px-5 py-16 text-center sm:px-8 lg:py-20">
<span className="border-border bg-muted/50 text-muted-foreground inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium">
<span
aria-hidden="true"
className="bg-primary size-1.5 rounded-full"
/>
One builder · every team
</span>
<HeroTextAnimation
animation="rotating-keyword"
ariaLabel="Launch pages for startups, agencies, product teams, and founders."
duration={0.34}
id="hero-text-rotating-keyword-heading"
rotatingKeywordIndex={keywordIndex}
rotatingKeywordOptions={rotatingKeywords}
rotatingKeywordPrefix="Launch pages for "
text="Launch pages for startups, agencies, product teams, and founders."
className="font-heading text-foreground [&_[data-slot=hero-text-animation-rotating-keyword]]:text-primary mx-auto mt-7 max-w-2xl text-4xl leading-[1.06] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl [&_[data-slot=hero-text-animation-motion]]:justify-center"
/>
<p className="text-muted-foreground mx-auto mt-6 max-w-xl text-base leading-7 sm:text-lg">
One flexible page builder that speaks every team's language.
Ship a launch that fits the audience — without a redesign for each
one.
</p>
<div
className="mt-8 flex flex-wrap items-center justify-center gap-2"
aria-hidden="true"
>
<span className="text-muted-foreground mr-1 text-xs font-medium tracking-wide uppercase">
Built for
</span>
{audiences.map((audience) => (
<span
key={audience}
className="border-border bg-background text-foreground inline-flex items-center rounded-full border px-3 py-1 text-sm font-medium"
>
{audience}
</span>
))}
</div>
<div className="mt-9 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Button asChild size="lg" rightIcon={<ArrowRight />}>
<a href="#installation">Start building free</a>
</Button>
<Button asChild size="lg" variant="ghost" leftIcon={<Play />}>
<a href="#examples">Watch the tour</a>
</Button>
</div>
<div className="mt-10 flex items-center justify-center gap-3">
<ul className="flex -space-x-2" aria-hidden="true">
{teamAvatars.map(({ initials, tone }) => (
<li
key={initials}
className={`border-background grid size-8 place-items-center rounded-full border-2 text-[11px] font-semibold ${tone}`}
>
{initials}
</li>
))}
</ul>
<p className="text-muted-foreground text-sm">
Trusted across 12 departments at 4,000+ companies
</p>
</div>
</div>
</section>
</HeroTextAnimationProvider>
);
}AI product hero — gradient highlight
A brand-forward, centered layout with soft glow backdrops, a one-shot gradient sweep, and an early-access email capture.
Write copy that sounds unmistakably you.
Trained on your best pages, our model drafts launch copy, ads, and emails that stay on-brand — then hands you the edit, never the other way around.
- No credit card
- 2-minute setup
- Cancel anytime
Show sourceexamples/hero-text-animation/gradient-highlight-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { Check, Sparkles } from "lucide-react";
const reassurances = ["No credit card", "2-minute setup", "Cancel anytime"];
export function HeroTextAnimationGradientHighlightHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-gradient-highlight-heading"
className="bg-background text-foreground border-border relative isolate overflow-hidden rounded-md border"
>
<span
aria-hidden="true"
className="bg-primary/20 pointer-events-none absolute -top-24 left-1/2 -z-10 size-72 -translate-x-1/2 rounded-full blur-3xl"
/>
<span
aria-hidden="true"
className="bg-info/20 pointer-events-none absolute -top-10 right-10 -z-10 size-56 rounded-full blur-3xl"
/>
<div className="mx-auto max-w-3xl px-5 py-16 text-center sm:px-8 lg:py-24">
<span className="border-border bg-background/70 text-foreground inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium backdrop-blur">
<Sparkles aria-hidden="true" className="text-primary size-3.5" />
New — AI that writes in your voice
</span>
<HeroTextAnimation
animation="gradient-highlight"
duration={1.8}
id="hero-text-gradient-highlight-heading"
repeat
repeatDelay={2}
text="Write copy that sounds unmistakably you."
className="font-heading text-foreground mx-auto mt-7 max-w-2xl text-4xl leading-[1.05] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mx-auto mt-6 max-w-xl text-base leading-7 sm:text-lg">
Trained on your best pages, our model drafts launch copy, ads, and
emails that stay on-brand — then hands you the edit, never the other
way around.
</p>
<div className="mx-auto mt-9 flex max-w-md flex-col gap-2.5 sm:flex-row">
<label htmlFor="gradient-hero-email" className="sr-only">
Work email
</label>
<input
id="gradient-hero-email"
type="email"
inputMode="email"
placeholder="you@company.com"
className="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring focus-visible:ring-offset-background h-11 min-w-0 flex-1 rounded-md border px-4 text-sm outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
/>
<Button size="lg" className="shrink-0">
Get early access
</Button>
</div>
<ul className="text-muted-foreground mt-5 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-sm">
{reassurances.map((item) => (
<li key={item} className="inline-flex items-center gap-1.5">
<Check aria-hidden="true" className="text-success size-4" />
{item}
</li>
))}
</ul>
</div>
</section>
</HeroTextAnimationProvider>
);
}Luxury launch hero — blur focus
A cinematic, minimal product-launch layout with generous whitespace, a soft vignette backdrop, and a single film CTA.
Introducing
Aura · Series 02
Designed to disappear into your day.
Ambient light that reads the room and fades from view. Nothing to configure. Nothing to notice — until you would miss it.
Show sourceexamples/hero-text-animation/blur-focus-hero.tsx
"use client";
import type { CSSProperties } from "react";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowRight, Play } from "lucide-react";
const backdropStyle: CSSProperties = {
backgroundImage:
"radial-gradient(80% 60% at 50% 0%, color-mix(in oklab, var(--dt-color-primary) 14%, transparent), transparent 70%), linear-gradient(to bottom, color-mix(in oklab, var(--dt-color-foreground) 5%, transparent), transparent 40%)",
};
export function HeroTextAnimationBlurFocusHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-blur-focus-heading"
className="bg-background text-foreground border-border relative overflow-hidden rounded-md border"
>
<span
aria-hidden="true"
style={backdropStyle}
className="pointer-events-none absolute inset-0"
/>
<div className="relative mx-auto flex max-w-2xl flex-col items-center px-5 py-20 text-center sm:px-8 lg:py-28">
<p className="text-muted-foreground text-xs font-medium tracking-[0.32em] uppercase">
Introducing
</p>
<p className="font-heading text-foreground mt-3 text-sm font-semibold tracking-[0.4em] uppercase">
Aura · Series 02
</p>
<HeroTextAnimation
animation="blur-focus"
duration={1.2}
id="hero-text-blur-focus-heading"
repeat
repeatDelay={2.4}
text="Designed to disappear into your day."
className="font-heading text-foreground mt-9 max-w-2xl text-4xl leading-[1.05] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mt-7 max-w-md text-base leading-7 sm:text-lg">
Ambient light that reads the room and fades from view. Nothing to
configure. Nothing to notice — until you would miss it.
</p>
<div className="mt-9">
<Button asChild size="lg" variant="outline" leftIcon={<Play />}>
<a href="#examples">Watch the film</a>
</Button>
</div>
</div>
<div className="border-border relative flex flex-col items-center justify-between gap-3 border-t px-5 py-5 text-sm sm:flex-row sm:px-8 lg:px-12">
<span className="text-muted-foreground tracking-wide">
Available Fall 2026
</span>
<a
href="#installation"
className="text-foreground focus-visible:ring-ring focus-visible:ring-offset-background group inline-flex items-center gap-1.5 rounded-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
Reserve yours
<ArrowRight
aria-hidden="true"
className="size-4 transition-transform group-hover:translate-x-0.5"
/>
</a>
</div>
</section>
</HeroTextAnimationProvider>
);
}Photography studio hero — word-by-word blur focus
The same blur-focus reveal split per word: each word racks from soft focus into clarity in sequence via the splitBy word prop, in an editorial studio layout.
Aperture Studio
Every frame pulled into perfect focus.
A photography practice for brands that sweat the details. Each word racks into focus the way our lenses do — deliberately, one plane at a time, until the whole picture is sharp.
Show sourceexamples/hero-text-animation/word-blur-focus-hero.tsx
"use client";
import type { CSSProperties } from "react";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowRight, Aperture } from "lucide-react";
const backdropStyle: CSSProperties = {
backgroundImage:
"radial-gradient(120% 80% at 15% 0%, color-mix(in oklab, var(--dt-color-primary) 12%, transparent), transparent 60%), linear-gradient(to bottom, color-mix(in oklab, var(--dt-color-foreground) 6%, transparent), transparent 45%)",
};
export function HeroTextAnimationWordBlurFocusHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-word-blur-focus-heading"
className="bg-background text-foreground border-border relative overflow-hidden rounded-md border"
>
<span
aria-hidden="true"
style={backdropStyle}
className="pointer-events-none absolute inset-0"
/>
<div className="relative mx-auto flex max-w-3xl flex-col px-5 py-20 sm:px-8 lg:py-28">
<p className="text-muted-foreground inline-flex items-center gap-2 text-xs font-medium tracking-[0.32em] uppercase">
<Aperture aria-hidden="true" className="text-primary size-4" />
Aperture Studio
</p>
<HeroTextAnimation
animation="blur-focus"
splitBy="word"
duration={0.7}
stagger={0.09}
id="hero-text-word-blur-focus-heading"
repeat
repeatDelay={2.2}
text="Every frame pulled into perfect focus."
className="font-heading text-foreground mt-8 max-w-2xl text-4xl leading-[1.05] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mt-7 max-w-lg text-base leading-7 sm:text-lg">
A photography practice for brands that sweat the details. Each word
racks into focus the way our lenses do — deliberately, one plane at
a time, until the whole picture is sharp.
</p>
<div className="mt-9 flex flex-col gap-3 sm:flex-row sm:items-center">
<Button asChild size="lg" rightIcon={<ArrowRight />}>
<a href="#installation">Book a session</a>
</Button>
<a
href="#examples"
className="text-foreground focus-visible:ring-ring focus-visible:ring-offset-background group inline-flex items-center gap-1.5 rounded-sm text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
>
View the portfolio
<ArrowRight
aria-hidden="true"
className="size-4 transition-transform group-hover:translate-x-0.5"
/>
</a>
</div>
</div>
<div className="border-border text-muted-foreground relative flex items-center justify-between border-t px-5 py-3.5 text-xs tracking-[0.18em] uppercase sm:px-8">
<span>Word-by-word focus</span>
<span aria-hidden="true">Soft → sharp</span>
</div>
</section>
</HeroTextAnimationProvider>
);
}Conversion hero — kinetic emphasis pop
A marketing layout that pops key value words (reinforced by weight, color, and underline) above a star-rating proof line and a metric row.
Turn more visitors into revenue, faster.
Run experiments on every headline, offer, and CTA — then let the winners ship themselves. Built for teams that grow on evidence, not opinions.
- 2.4×
- more pipeline from the same traffic
- −38%
- wasted ad spend in 60 days
- 6 min
- to launch your first test
Show sourceexamples/hero-text-animation/kinetic-emphasis-pop-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowRight, Star, TrendingUp } from "lucide-react";
const metrics = [
{ value: "2.4×", label: "more pipeline from the same traffic" },
{ value: "−38%", label: "wasted ad spend in 60 days" },
{ value: "6 min", label: "to launch your first test" },
];
export function HeroTextAnimationKineticEmphasisPopHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-kinetic-emphasis-heading"
className="bg-background text-foreground border-border overflow-hidden rounded-md border"
>
<div className="mx-auto max-w-3xl px-5 py-16 text-center sm:px-8 lg:py-20">
<div className="text-muted-foreground flex items-center justify-center gap-2 text-sm">
<span className="flex" aria-hidden="true">
{Array.from({ length: 5 }).map((_, index) => (
<Star
key={index}
className="fill-warning text-warning size-4"
/>
))}
</span>
<span>
Rated <span className="text-foreground font-semibold">4.9/5</span>{" "}
by 4,000+ growth teams
</span>
</div>
<HeroTextAnimation
animation="kinetic-emphasis-pop"
duration={0.5}
stagger={0.07}
emphasisWords={["revenue", "faster"]}
id="hero-text-kinetic-emphasis-heading"
repeat
repeatDelay={2}
text="Turn more visitors into revenue, faster."
className="font-heading text-foreground mx-auto mt-6 max-w-2xl text-4xl leading-[1.06] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mx-auto mt-6 max-w-xl text-base leading-7 sm:text-lg">
Run experiments on every headline, offer, and CTA — then let the
winners ship themselves. Built for teams that grow on evidence, not
opinions.
</p>
<div className="mt-9 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Button asChild size="lg" rightIcon={<ArrowRight />}>
<a href="#installation">Start free trial</a>
</Button>
<Button asChild size="lg" variant="outline">
<a href="#examples">Book a demo</a>
</Button>
</div>
</div>
<dl className="border-border grid border-t sm:grid-cols-3">
{metrics.map(({ value, label }, position) => (
<div
key={label}
className={`px-5 py-8 text-center sm:px-8 ${
position > 0
? "border-border border-t sm:border-t-0 sm:border-l"
: ""
}`}
>
<dt className="text-primary font-heading inline-flex items-center gap-1.5 text-3xl font-semibold tracking-tight sm:text-4xl">
{position === 0 ? (
<TrendingUp aria-hidden="true" className="size-6" />
) : null}
{value}
</dt>
<dd className="text-muted-foreground mx-auto mt-2 max-w-[16rem] text-sm leading-6">
{label}
</dd>
</div>
))}
</dl>
</section>
</HeroTextAnimationProvider>
);
}Campaign poster hero — SVG stroke draw
A design-forward poster layout with edition markers and a wordmark whose letterforms trace their outline and then fill, with the real heading kept available to assistive technology.
The 2026 brand refresh
Make it unforgettable.
A wordmark that draws itself, a palette that shifts with the light, and a system built to carry a brand across every surface.
Show sourceexamples/hero-text-animation/svg-stroke-draw-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowRight } from "lucide-react";
export function HeroTextAnimationSvgStrokeDrawHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-svg-stroke-draw-heading"
className="bg-background text-foreground border-border overflow-hidden rounded-md border"
>
<div className="border-border text-muted-foreground grid grid-cols-3 border-b text-xs font-medium tracking-[0.22em] uppercase">
<span className="px-5 py-3.5 sm:px-8">Dethink</span>
<span className="border-border hidden border-x px-5 py-3.5 text-center sm:block">
Campaign · 2026
</span>
<span className="px-5 py-3.5 text-right sm:px-8">№ 01</span>
</div>
<div className="mx-auto max-w-3xl px-5 py-20 text-center sm:px-8 lg:py-28">
<p className="text-muted-foreground text-xs font-medium tracking-[0.3em] uppercase">
The 2026 brand refresh
</p>
<HeroTextAnimation
animation="svg-stroke-draw"
duration={1.4}
id="hero-text-svg-stroke-draw-heading"
text={"Make it\nunforgettable."}
className="font-heading text-foreground mx-auto mt-8 max-w-2xl font-semibold tracking-tight"
/>
<p className="text-muted-foreground mx-auto mt-10 max-w-md text-base leading-7 sm:text-lg">
A wordmark that draws itself, a palette that shifts with the light,
and a system built to carry a brand across every surface.
</p>
<div className="mt-9">
<Button asChild size="lg" rightIcon={<ArrowRight />}>
<a href="#installation">See the identity system</a>
</Button>
</div>
</div>
<div className="border-border text-muted-foreground flex items-center justify-between border-t px-5 py-3.5 text-xs tracking-[0.18em] uppercase sm:px-8">
<span>Design-forward hero</span>
<span aria-hidden="true">Outline → fill</span>
</div>
</section>
</HeroTextAnimationProvider>
);
}Storytelling hero — scroll responsive
A long-scroll narrative layout with chapter markers and a scroll cue, where the opening line subtly recedes on scroll while staying readable.
A field guide · Chapter 01
Every product starts as a quiet, stubborn idea.
Scroll and the opening line steps back, making room for the story. Nothing essential moves out of reach — the heading stays readable the whole way down.
Scroll to begin
01 — The quiet idea
It rarely arrives as a plan. It shows up as a small, nagging sense that something could be better — and refuses to leave until you build it.
Show sourceexamples/hero-text-animation/scroll-responsive-hero.tsx
"use client";
import {
Button,
HeroTextAnimation,
HeroTextAnimationProvider,
} from "@dethink/components";
import { ArrowRight, ChevronDown } from "lucide-react";
const chapters = [
{ index: "01", title: "The quiet idea", active: true },
{ index: "02", title: "The stubborn build" },
{ index: "03", title: "The first believer" },
];
export function HeroTextAnimationScrollResponsiveHero() {
return (
<HeroTextAnimationProvider>
<section
aria-labelledby="hero-text-scroll-responsive-heading"
className="bg-background text-foreground border-border overflow-hidden rounded-md border"
>
<div className="mx-auto grid max-w-5xl gap-10 px-5 py-16 sm:px-8 lg:grid-cols-[minmax(0,1fr)_16rem] lg:px-10 lg:py-24">
<div className="min-w-0">
<p className="text-muted-foreground text-xs font-medium tracking-[0.24em] uppercase">
A field guide · Chapter 01
</p>
<HeroTextAnimation
animation="scroll-responsive"
id="hero-text-scroll-responsive-heading"
text="Every product starts as a quiet, stubborn idea."
className="font-heading text-foreground mt-6 max-w-2xl text-4xl leading-[1.06] font-semibold tracking-tight text-balance sm:text-5xl lg:text-6xl"
/>
<p className="text-muted-foreground mt-7 max-w-lg text-base leading-7 sm:text-lg">
Scroll and the opening line steps back, making room for the story.
Nothing essential moves out of reach — the heading stays readable
the whole way down.
</p>
<div className="mt-8">
<Button
asChild
size="lg"
variant="outline"
rightIcon={<ArrowRight />}
>
<a href="#examples">Read the story</a>
</Button>
</div>
<p className="text-muted-foreground mt-12 inline-flex items-center gap-2 text-xs font-medium tracking-[0.18em] uppercase">
<ChevronDown
aria-hidden="true"
className="size-4 animate-bounce motion-reduce:animate-none"
/>
Scroll to begin
</p>
</div>
<nav
aria-label="Chapters"
className="border-border lg:border-l lg:pl-8"
>
<p className="text-muted-foreground text-xs font-medium tracking-[0.2em] uppercase">
In this story
</p>
<ol className="mt-4 space-y-4">
{chapters.map(({ index, title, active }) => (
<li key={index} className="flex items-start gap-3">
<span
aria-hidden="true"
className={`mt-1.5 size-1.5 shrink-0 rounded-full ${
active ? "bg-primary" : "bg-border"
}`}
/>
<span className="min-w-0">
<span
className={`font-mono text-xs ${
active ? "text-primary" : "text-muted-foreground"
}`}
>
{index}
</span>
<span
className={`mt-0.5 block text-sm font-medium ${
active ? "text-foreground" : "text-muted-foreground"
}`}
>
{title}
</span>
</span>
</li>
))}
</ol>
</nav>
</div>
<div className="border-border bg-muted/30 border-t px-5 py-8 sm:px-8 lg:px-10">
<div className="mx-auto max-w-2xl">
<p className="text-muted-foreground font-mono text-xs tracking-widest">
01 — The quiet idea
</p>
<p className="text-foreground mt-3 text-lg leading-8">
It rarely arrives as a plan. It shows up as a small, nagging sense
that something could be better — and refuses to leave until you
build it.
</p>
</div>
</div>
</section>
</HeroTextAnimationProvider>
);
}HeroTextAnimation renders real text and uses a decorative visual layer only for the animation.
Avoid scramble and decrypt effects for legal, medical, pricing, or compliance-critical wording. Use static text for copy where every character must be immediately reviewable. Limit blur focus to short cinematic hero headings that can finish resolving before users need to read the copy. Use SVG stroke draw for short headings only; it renders scalable letterforms that trace and fill, does not auto-wrap, and keeps the real heading available to assistive technology. Use scroll-responsive text only when the full heading is readable before the first scroll and the next content remains in normal document flow.
| Prop | What it does | Default |
|---|---|---|
textstring | Final hero copy. It is rendered as stable accessible text and split only for the visual animation layer. | Not set |
animation"stagger-words" | "masked-curtain" | "typewriter" | "scramble-decrypt" | "rotating-keyword" | "gradient-highlight" | "blur-focus" | "kinetic-emphasis-pop" | "svg-stroke-draw" | "scroll-responsive" | Animation style. Use staggered words for the safest default, masked curtain for line-by-line reveals, typewriter for short developer/product hero copy, scramble-decrypt for deterministic decorative glyph resolution, rotating-keyword for a stable sentence with a decorative swapping slot, gradient-highlight for a one-shot tokenized highlight sweep over a complete phrase, blur-focus for short cinematic hero headings that resolve quickly into crisp readable text, kinetic-emphasis-pop for one or two statically emphasized words with a subtle one-shot scale accent, svg-stroke-draw for a scalable letterform outline-to-fill trace on short headings, or scroll-responsive for a subtle bounded first-scroll response on storytelling pages. | "stagger-words" |
as"h1" | "h2" | "p" | "span" | Semantic element for the root. Use h1 or h2 for actual hero headings. | "h1" |
ariaLabelstring | Stable label announced by assistive technology when the visual fragments need different copy. | text |
splitBy"word" | "line" | Controls whether the visual layer staggers each word or explicit newline-delimited lines. | "word" |
trigger"mount" | "in-view" | "manual" | Runs on mount, when the hero enters the viewport, or from the controlled active state. | "mount" |
reducedMotionStrategy"static" | "opacity-only" | Controls the fallback when reduced motion is active. Transform motion is removed either way. | "opacity-only" |
showCaretboolean | Shows a decorative typewriter caret while typing. The caret is removed after completion and never loops. | true |
repeatboolean | Replays the decorative visual animation after it completes. Keep it off for static content and enable it for demos or controlled preview surfaces. | false |
repeatDelaynumber | Delay in seconds before an enabled repeat starts the animation again. Reduced-motion rendering never schedules repeats. | 1.8 |
rotatingKeywordOptionsreadonly string[] | Keyword or short phrase options for the rotating-keyword visual slot. The slot reserves the longest option so the headline layout stays stable. | [text] |
rotatingKeywordPrefix / rotatingKeywordSuffixstring | Visible decorative text rendered before and after the keyword slot. Keep text as the complete stable sentence for assistive technology. | "" / "" |
rotatingKeywordIndex / defaultRotatingKeywordIndexnumber | Controls or initializes the active keyword. Use rotatingKeywordIndex with onRotatingKeywordIndexChange for manual selectors. | undefined / 0 |
autoRotateKeywords / rotatingKeywordIntervalboolean / number | Opt-in keyword auto rotation. Timers are bounded and stop within five seconds; reduced-motion rendering never schedules them. | false / 1.6 |
emphasisWordsreadonly string[] | Word terms to emphasize for kinetic-emphasis-pop. Matching is case-insensitive and ignores surrounding punctuation. The component uses the first two resolved words. | [] |
emphasisWordIndicesreadonly number[] | Zero-based word indices to emphasize for kinetic-emphasis-pop. Indices resolve before emphasisWords so precise targeting can override repeated terms. | [] |
svgPathDatastring | readonly string[] | readonly { d: string; strokeWidth?: number }[] | Deprecated. svg-stroke-draw now traces the heading's own letterforms (outline → fill) instead of a decorative accent path, so this prop is ignored. Retained only for backwards-compatible type-checking. | undefined |
svgViewBoxstring | Optional view box override for the svg-stroke-draw letter trace. Leave unset (recommended) to frame the text automatically from its measured glyph metrics; provide four numeric values with positive width and height to pin the framing. | auto (measured) |
svgAccessibleTitlestring | Optional accessible title for the traced SVG. Leave unset for normal hero copy so assistive technology reads only the real HTML text; set it to expose the SVG as a labelled image. | undefined |
delay / duration / staggernumber | Timing controls in seconds. Typewriter, scramble-decrypt, gradient-highlight, blur-focus, kinetic-emphasis-pop, and svg-stroke-draw use duration as bounded total reveal time. Scramble-decrypt caps updates so it cannot run indefinitely or exceed three updates per second. Gradient-highlight, blur-focus, kinetic-emphasis-pop, and svg-stroke-draw run once by default and use the shared repeat mechanism only when repeat is enabled. Scroll-responsive is tied to page scroll instead of duration and does not replay. | 0.05 / 0.48 / 0.045; typewriter duration 1.1; scramble duration 1.2; rotating keyword duration 0.34; gradient highlight duration 0.9; blur focus duration 0.42; kinetic emphasis duration 0.42; svg stroke draw duration 1.4; scroll responsive maps the first 220px of scroll |
blur-focus content guidanceshort hero heading | Limit blur-focus to short cinematic headings, ideally one line or under eight words. Do not combine the same heading with scale, parallax, or rotation; the component uses only small blur, opacity, and vertical translation, then finishes at blur(0px). | Not set |
kinetic-emphasis-pop content guidanceone or two important words | Use kinetic emphasis for one or two words whose importance is also clear from static styling. Reduced motion removes scale animation and keeps the color, weight, and underline emphasis. | Not set |
scroll-responsive content guidancestorytelling hero heading | Use scroll-responsive only when the complete heading is readable before scroll. The decorative layer maps the first 220px of page scroll to at most -32px of vertical movement and 92% opacity; reduced motion removes the scroll listener and transform style. | Not set |
svg-stroke-draw content guidanceshort single- or multi-line heading | svg-stroke-draw renders the heading as scalable SVG letterforms that trace their outline and then fill (outline → fill). It does not auto-wrap or use text-balance, so keep copy short and add hard line breaks (\n) for multi-line headings. The real HTML text stays available to assistive technology via a visually hidden copy. Reduced motion renders the final filled letters immediately and skips drawing. | Not set |
HeroTextAnimationProviderreducedMotion?: "user" | "always" | "never" | Wraps examples or app surfaces with MotionConfig and a testable reduced-motion override. | "user" |