Build faster
ship smarter
Features
- ✅ Fully prop-based - Customize everything via props
- ✅ TypeScript support - Full type safety
- ✅ Animated canvas gradients - Smooth, hardware-accelerated
- ✅ Flowing text gradient - Animated headline effect
- ✅ Floating orb effects - Three independent animations
- ✅ Staggered entrance - Sequential fade-in animations
- ✅ Optional sections - Badge, features, stats, social proof
- ✅ Flexible CTAs - Support for href or onClick
- ✅ Star ratings - Built-in rating display
- ✅ Company logos - Social proof section
- ✅ Fully responsive - Mobile-first design
- ✅ Performance optimized - 60fps animations
Installation
Copy and paste the following code into your project.
"use client";
import { Button } from "@/components/ui/button";
import { ArrowRight, CheckCircle2, Play, Sparkles, Star } from "lucide-react";
import { useEffect, useRef } from "react";
interface GradientHeroAnimatedProps {
badge?: {
icon?: React.ReactNode;
text: string;
};
headline: {
line1: string;
line2: string;
};
description: string;
primaryCTA: {
text: string;
href?: string;
onClick?: () => void;
};
secondaryCTA?: {
text: string;
href?: string;
onClick?: () => void;
};
features?: string[];
stats?: Array<{
value: string;
label: string;
}>;
socialProof?: {
rating?: number;
reviewCount?: string;
text?: string;
logos?: string[];
};
}
export function GradientHeroAnimated({
badge = {
icon: <Sparkles className="h-4 w-4" />,
text: "Introducing our new platform",
},
headline = {
line1: "Build the next",
line2: "big thing",
},
description = "Ship products faster with tools designed for modern teams. From idea to launch in record time.",
primaryCTA = {
text: "Get Started Free",
href: "#",
},
secondaryCTA = {
text: "Watch Demo",
href: "#",
},
features = ["No credit card required", "14-day free trial", "Cancel anytime"],
stats = [
{ value: "50K+", label: "Active Users" },
{ value: "99.9%", label: "Uptime" },
{ value: "4.9/5", label: "Rating" },
],
socialProof,
}: GradientHeroAnimatedProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let animationFrameId: number;
let time = 0;
const resize = () => {
canvas.width = canvas.offsetWidth * window.devicePixelRatio;
canvas.height = canvas.offsetHeight * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
};
resize();
window.addEventListener("resize", resize);
const animate = () => {
time += 0.002;
const width = canvas.offsetWidth;
const height = canvas.offsetHeight;
ctx.clearRect(0, 0, width, height);
// Large orange orb
const gradient1 = ctx.createRadialGradient(
width * (0.25 + Math.sin(time * 0.5) * 0.1),
height * (0.4 + Math.cos(time * 0.3) * 0.1),
0,
width * (0.25 + Math.sin(time * 0.5) * 0.1),
height * (0.4 + Math.cos(time * 0.3) * 0.1),
width * 0.4,
);
gradient1.addColorStop(0, "rgba(251, 146, 60, 0.35)");
gradient1.addColorStop(0.5, "rgba(249, 115, 22, 0.2)");
gradient1.addColorStop(1, "rgba(0, 0, 0, 0)");
ctx.fillStyle = gradient1;
ctx.fillRect(0, 0, width, height);
// Amber center orb
const gradient2 = ctx.createRadialGradient(
width * (0.6 + Math.cos(time * 0.4) * 0.08),
height * (0.5 + Math.sin(time * 0.6) * 0.08),
0,
width * (0.6 + Math.cos(time * 0.4) * 0.08),
height * (0.5 + Math.sin(time * 0.6) * 0.08),
width * 0.35,
);
gradient2.addColorStop(0, "rgba(245, 158, 11, 0.4)");
gradient2.addColorStop(0.5, "rgba(251, 191, 36, 0.2)");
gradient2.addColorStop(1, "rgba(0, 0, 0, 0)");
ctx.fillStyle = gradient2;
ctx.fillRect(0, 0, width, height);
// Yellow accent orb
const gradient3 = ctx.createRadialGradient(
width * (0.75 + Math.sin(time * 0.7) * 0.1),
height * (0.3 + Math.cos(time * 0.5) * 0.1),
0,
width * (0.75 + Math.sin(time * 0.7) * 0.1),
height * (0.3 + Math.cos(time * 0.5) * 0.1),
width * 0.3,
);
gradient3.addColorStop(0, "rgba(250, 204, 21, 0.3)");
gradient3.addColorStop(0.5, "rgba(234, 179, 8, 0.15)");
gradient3.addColorStop(1, "rgba(0, 0, 0, 0)");
ctx.fillStyle = gradient3;
ctx.fillRect(0, 0, width, height);
animationFrameId = requestAnimationFrame(animate);
};
animate();
return () => {
window.removeEventListener("resize", resize);
cancelAnimationFrame(animationFrameId);
};
}, []);
const handlePrimaryCTA = () => {
if (primaryCTA.onClick) {
primaryCTA.onClick();
} else if (primaryCTA.href) {
window.location.href = primaryCTA.href;
}
};
const handleSecondaryCTA = () => {
if (secondaryCTA?.onClick) {
secondaryCTA.onClick();
} else if (secondaryCTA?.href) {
window.location.href = secondaryCTA.href;
}
};
return (
<section className="relative flex min-h-screen items-center overflow-hidden bg-neutral-950">
<canvas
ref={canvasRef}
className="absolute inset-0 h-full w-full"
style={{ filter: "blur(90px)" }}
/>
<div className="absolute inset-0 bg-gradient-to-t from-neutral-950 via-transparent to-neutral-950/50" />
<div className="container relative z-10 mx-auto px-6 py-24">
<div className="mx-auto max-w-4xl text-center">
{badge && (
<div className="mb-8 inline-flex animate-in fade-in slide-in-from-bottom-3 items-center gap-2 rounded-full border border-orange-500/20 bg-orange-500/10 px-4 py-2 backdrop-blur-xl duration-700">
<span className="text-orange-400">{badge.icon}</span>
<span className="text-sm font-medium text-white/90">
{badge.text}
</span>
</div>
)}
<h1 className="mb-8 animate-in fade-in slide-in-from-bottom-4 text-6xl font-bold leading-[1.05] tracking-tight text-white duration-1000 sm:text-7xl md:text-8xl">
{headline.line1}
<br />
<span className="animate-gradient bg-gradient-to-r from-orange-400 via-amber-300 to-yellow-400 bg-[length:200%_auto] bg-clip-text text-transparent">
{headline.line2}
</span>
</h1>
<p className="mb-8 animate-in fade-in slide-in-from-bottom-5 text-xl leading-relaxed text-neutral-400 duration-1000 sm:text-2xl">
{description}
</p>
{features && features.length > 0 && (
<div className="mb-12 flex animate-in fade-in slide-in-from-bottom-6 flex-wrap items-center justify-center gap-6 text-sm text-neutral-400 duration-1000">
{features.map((feature, index) => (
<div key={index} className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-400" />
<span>{feature}</span>
</div>
))}
</div>
)}
<div className="flex animate-in fade-in slide-in-from-bottom-7 flex-col items-center justify-center gap-4 duration-1000 sm:flex-row">
<Button
size="lg"
onClick={handlePrimaryCTA}
className="group h-14 bg-gradient-to-r from-orange-500 to-amber-500 px-10 text-lg font-semibold text-white shadow-lg shadow-orange-500/25 transition-all hover:scale-[1.02] hover:shadow-xl hover:shadow-orange-500/40"
>
{primaryCTA.text}
<ArrowRight className="ml-2 h-5 w-5 transition-transform group-hover:translate-x-1" />
</Button>
{secondaryCTA && (
<Button
size="lg"
variant="ghost"
onClick={handleSecondaryCTA}
className="group h-14 px-10 text-lg font-semibold text-neutral-300 transition-colors hover:bg-white/5 hover:text-white"
>
<Play className="mr-2 h-5 w-5 transition-transform group-hover:scale-110" />
{secondaryCTA.text}
</Button>
)}
</div>
{socialProof && (
<div className="mt-12 animate-in fade-in slide-in-from-bottom-8 duration-1000">
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
{socialProof.rating && (
<div className="flex items-center gap-2">
<div className="flex">
{Array.from({ length: 5 }).map((_, i) => (
<Star
key={i}
className={`h-5 w-5 ${
i < Math.floor(socialProof.rating!)
? "fill-yellow-400 text-yellow-400"
: "fill-neutral-700 text-neutral-700"
}`}
/>
))}
</div>
<span className="text-sm font-semibold text-white">
{socialProof.rating}/5
</span>
{socialProof.reviewCount && (
<span className="text-sm text-neutral-500">
({socialProof.reviewCount} reviews)
</span>
)}
</div>
)}
{socialProof.text && (
<span className="text-sm text-neutral-500">
{socialProof.text}
</span>
)}
</div>
{socialProof.logos && socialProof.logos.length > 0 && (
<div className="mt-8 flex flex-wrap items-center justify-center gap-8 opacity-50">
{socialProof.logos.map((logo, index) => (
<div
key={index}
className="text-lg font-semibold text-white transition-opacity hover:opacity-100"
>
{logo}
</div>
))}
</div>
)}
</div>
)}
{stats && stats.length > 0 && (
<div className="mt-20 grid animate-in fade-in slide-in-from-bottom-9 grid-cols-3 gap-8 border-t border-white/5 pt-12 duration-1000">
{stats.map((stat, index) => (
<div
key={index}
className="group transition-transform hover:scale-105"
>
<div className="mb-2 text-3xl font-bold text-white sm:text-4xl">
{stat.value}
</div>
<div className="text-sm text-neutral-500">{stat.label}</div>
</div>
))}
</div>
)}
</div>
</div>
<div className="pointer-events-none absolute inset-0">
<div className="absolute left-[15%] top-[25%] h-[400px] w-[400px] animate-float-slow rounded-full bg-orange-500/20 blur-[80px]" />
<div className="absolute right-[20%] top-[35%] h-[350px] w-[350px] animate-float-delayed rounded-full bg-amber-500/20 blur-[80px]" />
<div className="absolute bottom-[30%] left-[50%] h-[300px] w-[300px] -translate-x-1/2 animate-float rounded-full bg-yellow-500/10 blur-[80px]" />
</div>
<style jsx>{`
@keyframes gradient {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
@keyframes float {
0%,
100% {
transform: translate(0, 0);
}
33% {
transform: translate(20px, -20px);
}
66% {
transform: translate(-15px, 15px);
}
}
@keyframes float-delayed {
0%,
100% {
transform: translate(0, 0);
}
33% {
transform: translate(-25px, 20px);
}
66% {
transform: translate(20px, -15px);
}
}
@keyframes float-slow {
0%,
100% {
transform: translate(0, 0);
}
50% {
transform: translate(15px, 15px);
}
}
.animate-gradient {
animation: gradient 8s ease infinite;
}
.animate-float {
animation: float 15s ease-in-out infinite;
}
.animate-float-delayed {
animation: float-delayed 18s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 20s ease-in-out infinite;
}
`}</style>
</section>
);
}Update the import paths to match your project setup.
import { Button } from "@/components/ui/button";Usage
Basic Usage
import GradientHeroAnimated from "@/components/ui/gradient-hero-animated";
export default function HomePage() {
return <GradientHeroAnimated />;
}With Custom Props
import GradientHeroAnimated from "@/components/ui/gradient-hero-animated";
import { Sparkles } from "lucide-react";
export default function HomePage() {
return (
<GradientHeroAnimated
badge={{
icon: <Sparkles className="h-4 w-4" />,
text: "New: AI-Powered Features",
}}
headline={{
line1: "Build faster",
line2: "ship smarter",
}}
description="The complete platform for modern teams. Ship products faster with tools designed for scale."
primaryCTA={{
text: "Start Free Trial",
onClick: () => console.log("Primary CTA clicked"),
}}
secondaryCTA={{
text: "Watch Demo",
href: "/demo",
}}
features={[
"No credit card required",
"14-day free trial",
"Cancel anytime",
]}
stats={[
{ value: "100K+", label: "Active Users" },
{ value: "99.9%", label: "Uptime" },
{ value: "4.9/5", label: "Rating" },
]}
socialProof={{
rating: 4.9,
reviewCount: "2,500+",
text: "Trusted by teams worldwide",
logos: ["Google", "Microsoft", "Amazon", "Netflix"],
}}
/>
);
}With Router Navigation
"use client";
import { useRouter } from "next/navigation";
import GradientHeroAnimated from "@/components/ui/gradient-hero-animated";
export default function HomePage() {
const router = useRouter();
return (
<GradientHeroAnimated
headline={{
line1: "Build the next",
line2: "big thing",
}}
description="Ship products faster with tools designed for modern teams."
primaryCTA={{
text: "Get Started Free",
onClick: () => router.push("/signup"),
}}
secondaryCTA={{
text: "Learn More",
onClick: () => router.push("/about"),
}}
/>
);
}Props
GradientHeroAnimatedProps
| Prop | Type | Default | Description |
|---|---|---|---|
badge | object | { icon: <Sparkles />, text: "Introducing our new platform" } | Optional badge at the top |
badge.icon | React.ReactNode | <Sparkles /> | Icon for the badge |
badge.text | string | Required | Badge text |
headline | object | { line1: "Build the next", line2: "big thing" } | Headline configuration |
headline.line1 | string | Required | First line of headline |
headline.line2 | string | Required | Second line (gets animated gradient) |
description | string | Default text | Hero description |
primaryCTA | object | Required | Primary call-to-action button |
primaryCTA.text | string | Required | Button text |
primaryCTA.href | string | Optional | Link URL |
primaryCTA.onClick | () => void | Optional | Click handler |
secondaryCTA | object | Optional | Secondary call-to-action button |
secondaryCTA.text | string | Required | Button text |
secondaryCTA.href | string | Optional | Link URL |
secondaryCTA.onClick | () => void | Optional | Click handler |
features | string[] | ["No credit card required", ...] | Feature list with checkmarks |
stats | Array<{value: string, label: string}> | Default stats | Stats to display |
socialProof | object | Optional | Social proof section |
socialProof.rating | number | Optional | Star rating (0-5) |
socialProof.reviewCount | string | Optional | Number of reviews |
socialProof.text | string | Optional | Social proof text |
socialProof.logos | string[] | Optional | Company logos |
Customization
Minimal Example
<GradientHeroAnimated
headline={{
line1: "Simple",
line2: "and clean",
}}
description="Less is more."
primaryCTA={{
text: "Get Started",
href: "/signup",
}}
features={undefined}
stats={undefined}
socialProof={undefined}
/>With All Features
<GradientHeroAnimated
badge={{ text: "🎉 Product Hunt #1 Product of the Day" }}
headline={{
line1: "The future of",
line2: "productivity",
}}
description="Everything you need to build, ship, and scale your product. Trusted by over 50,000 teams worldwide."
primaryCTA={{
text: "Start Free Trial",
onClick: handleSignup,
}}
secondaryCTA={{
text: "Schedule Demo",
onClick: handleDemo,
}}
features={[
"Free 14-day trial",
"No credit card required",
"Cancel anytime",
"24/7 support",
]}
stats={[
{ value: "50K+", label: "Active Users" },
{ value: "99.9%", label: "Uptime SLA" },
{ value: "4.9/5", label: "User Rating" },
]}
socialProof={{
rating: 4.9,
reviewCount: "5,000+",
text: "Loved by teams at",
logos: ["Stripe", "Vercel", "Linear", "GitHub", "Notion"],
}}
/>Change Gradient Colors
To change the animated gradient colors, you'll need to modify the canvas gradient stops in the component. Here are some preset color schemes:
Orange/Amber/Yellow (Default):
gradient1.addColorStop(0, "rgba(251, 146, 60, 0.35)");
gradient2.addColorStop(0, "rgba(245, 158, 11, 0.4)");
gradient3.addColorStop(0, "rgba(250, 204, 21, 0.3)");Purple/Pink/Blue:
gradient1.addColorStop(0, "rgba(139, 92, 246, 0.35)");
gradient2.addColorStop(0, "rgba(236, 72, 153, 0.4)");
gradient3.addColorStop(0, "rgba(59, 130, 246, 0.3)");Green/Teal/Cyan:
gradient1.addColorStop(0, "rgba(16, 185, 129, 0.35)");
gradient2.addColorStop(0, "rgba(20, 184, 166, 0.4)");
gradient3.addColorStop(0, "rgba(6, 182, 212, 0.3)");Red/Orange/Pink:
gradient1.addColorStop(0, "rgba(239, 68, 68, 0.35)");
gradient2.addColorStop(0, "rgba(249, 115, 22, 0.4)");
gradient3.addColorStop(0, "rgba(236, 72, 153, 0.3)");Best Practices
- Color contrast - Ensure text remains readable against animated backgrounds
- Performance testing - Test on lower-end devices, consider reducing animation complexity for mobile
- Blur intensity - Adjust blur values if gradients are too intense or subtle
- Animation speed - Slower animations (15-20s) feel more premium than fast ones
- Content hierarchy - Keep the layout clean and focused on the main message
Use Cases
Perfect for:
- Modern SaaS platforms
- Tech startups
- AI/ML products
- Creative tools
- Design software
- Developer platforms
- Premium services
- Product launches
Examples
Similar styles used by:
- Vercel - Deployment platform with animated gradients
- Stripe - Payment platform with elegant animations
- Linear - Issue tracking with smooth transitions
- Framer - Design tool with flowing gradients
TypeScript Interface
interface GradientHeroAnimatedProps {
badge?: {
icon?: React.ReactNode;
text: string;
};
headline: {
line1: string;
line2: string; // This line gets the animated gradient
};
description: string;
primaryCTA: {
text: string;
href?: string;
onClick?: () => void;
};
secondaryCTA?: {
text: string;
href?: string;
onClick?: () => void;
};
features?: string[];
stats?: Array<{
value: string;
label: string;
}>;
socialProof?: {
rating?: number;
reviewCount?: string;
text?: string;
logos?: string[];
};
}@media (prefers-reduced-motion: reduce) {
.animate-gradient,
.animate-float,
.animate-float-delayed,
.animate-float-slow {
animation: none;
}
}