Gradient Footer
Original · freegradient footer
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/footx-gradient.json"use client";
import { useId, useRef, useState, type FormEvent, type ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
type FooterLink = { label: string; href: string };
type FooterColumn = { id: string; title: string; links: FooterLink[] };
type SocialLink = { name: string; href: string; icon: ReactNode };
type SubscribeState = "idle" | "invalid" | "success";
const COLUMNS: FooterColumn[] = [
{
id: "product",
title: "Product",
links: [
{ label: "Uptime monitors", href: "#monitors" },
{ label: "Status pages", href: "#status-pages" },
{ label: "Incident timeline", href: "#incidents" },
{ label: "Integrations", href: "#integrations" },
{ label: "Pricing", href: "#pricing" },
],
},
{
id: "developers",
title: "Developers",
links: [
{ label: "REST & webhook API", href: "#api" },
{ label: "CLI reference", href: "#cli" },
{ label: "Terraform provider", href: "#terraform" },
{ label: "Changelog", href: "#changelog" },
{ label: "Service status", href: "#service-status" },
],
},
{
id: "company",
title: "Company",
links: [
{ label: "About Beacon", href: "#about" },
{ label: "Careers — 6 open roles", href: "#careers" },
{ label: "Engineering blog", href: "#blog" },
{ label: "Press kit", href: "#press" },
{ label: "Contact sales", href: "#contact" },
],
},
{
id: "legal",
title: "Legal",
links: [
{ label: "Privacy policy", href: "#privacy" },
{ label: "Terms of service", href: "#terms" },
{ label: "Data processing (DPA)", href: "#dpa" },
{ label: "99.99% SLA", href: "#sla" },
{ label: "Trust & security", href: "#security" },
],
},
];
const SOCIALS: SocialLink[] = [
{
name: "Beacon on X",
href: "https://x.com",
icon: (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className="h-[18px] w-[18px]">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.657l-5.214-6.817-5.966 6.817H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z" />
</svg>
),
},
{
name: "Beacon on GitHub",
href: "https://github.com",
icon: (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className="h-5 w-5">
<path d="M12 .5A11.5 11.5 0 0 0 .5 12a11.5 11.5 0 0 0 7.86 10.92c.58.1.79-.25.79-.56v-2c-3.2.7-3.88-1.37-3.88-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.2 1.77 1.2 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.56-.29-5.25-1.28-5.25-5.7 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.79 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.84 1.19 3.1 0 4.43-2.69 5.4-5.26 5.69.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56A11.5 11.5 0 0 0 23.5 12 11.5 11.5 0 0 0 12 .5Z" />
</svg>
),
},
{
name: "Beacon on LinkedIn",
href: "https://linkedin.com",
icon: (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className="h-5 w-5">
<path d="M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.44-2.13 2.94v5.67H9.35V9h3.42v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.46v6.28ZM5.34 7.43a2.07 2.07 0 1 1 0-4.14 2.07 2.07 0 0 1 0 4.14ZM7.12 20.45H3.55V9h3.57v11.45ZM22.22 0H1.77C.8 0 0 .78 0 1.74v20.52C0 23.22.8 24 1.77 24h20.45c.98 0 1.78-.78 1.78-1.74V1.74C24 .78 23.2 0 22.22 0Z" />
</svg>
),
},
{
name: "Beacon on YouTube",
href: "https://youtube.com",
icon: (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className="h-5 w-5">
<path d="M23.5 6.2a3.02 3.02 0 0 0-2.12-2.14C19.5 3.55 12 3.55 12 3.55s-7.5 0-9.38.51A3.02 3.02 0 0 0 .5 6.2 31.5 31.5 0 0 0 0 12a31.5 31.5 0 0 0 .5 5.8 3.02 3.02 0 0 0 2.12 2.14c1.88.51 9.38.51 9.38.51s7.5 0 9.38-.51a3.02 3.02 0 0 0 2.12-2.14A31.5 31.5 0 0 0 24 12a31.5 31.5 0 0 0-.5-5.8ZM9.6 15.57V8.43L15.8 12l-6.2 3.57Z" />
</svg>
),
},
{
name: "Beacon RSS feed",
href: "#rss",
icon: (
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" className="h-[18px] w-[18px]">
<path d="M4.5 15.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM3 8.5v3a9 9 0 0 1 9 9h3A12 12 0 0 0 3 8.5ZM3 3v3a15 15 0 0 1 15 15h3A18 18 0 0 0 3 3Z" />
</svg>
),
},
];
const REGIONS: FooterLink[] = [
{ label: "US East (Virginia)", href: "us-east" },
{ label: "EU West (Frankfurt)", href: "eu-west" },
{ label: "Asia (Singapore)", href: "asia-se" },
{ label: "South America (São Paulo)", href: "sa-east" },
];
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export default function FootxGradient() {
const reduce = useReducedMotion();
const baseId = useId();
const [email, setEmail] = useState("");
const [status, setStatus] = useState<SubscribeState>("idle");
const [region, setRegion] = useState(REGIONS[0].href);
const [open, setOpen] = useState<Record<string, boolean>>({});
const inputRef = useRef<HTMLInputElement>(null);
const emailId = `${baseId}-email`;
const emailMsgId = `${baseId}-email-msg`;
const toggleColumn = (id: string) =>
setOpen((prev) => ({ ...prev, [id]: !prev[id] }));
const handleSubscribe = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!EMAIL_RE.test(email.trim())) {
setStatus("invalid");
inputRef.current?.focus();
return;
}
setStatus("success");
setEmail("");
};
const scrollTop = () =>
window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
return (
<section className="relative w-full overflow-hidden bg-gradient-to-br from-indigo-100 via-violet-100 to-sky-100 text-slate-700 dark:from-indigo-950 dark:via-violet-950 dark:to-slate-950 dark:text-slate-300">
<style>{`
@keyframes footx-aurora-a {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(7%, -5%, 0) scale(1.18); }
}
@keyframes footx-aurora-b {
0%, 100% { transform: translate3d(0, 0, 0) scale(1.12); }
50% { transform: translate3d(-6%, 6%, 0) scale(1); }
}
@keyframes footx-shine {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
@keyframes footx-pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.78); }
}
@keyframes footx-ring {
0% { transform: scale(0.6); opacity: 0.7; }
100% { transform: scale(2.4); opacity: 0; }
}
.footx-aurora-a { animation: footx-aurora-a 20s ease-in-out infinite; }
.footx-aurora-b { animation: footx-aurora-b 26s ease-in-out infinite; }
.footx-shine {
background-size: 200% auto;
animation: footx-shine 7s linear infinite;
}
.footx-dot { animation: footx-pulse-dot 2.4s ease-in-out infinite; }
.footx-ring { animation: footx-ring 2.4s ease-out infinite; }
@media (prefers-reduced-motion: reduce) {
.footx-aurora-a, .footx-aurora-b, .footx-shine, .footx-dot, .footx-ring {
animation: none !important;
}
}
`}</style>
{/* Decorative animated gradient field */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="footx-aurora-a absolute -left-24 -top-24 h-[28rem] w-[28rem] rounded-full bg-indigo-400/40 blur-3xl dark:bg-indigo-600/25" />
<div className="footx-aurora-b absolute -right-20 top-10 h-[26rem] w-[26rem] rounded-full bg-sky-400/40 blur-3xl dark:bg-sky-600/20" />
<div className="footx-aurora-a absolute bottom-0 left-1/3 h-[24rem] w-[24rem] rounded-full bg-violet-400/30 blur-3xl dark:bg-violet-600/25" />
</div>
<div
aria-hidden="true"
className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-indigo-400/70 to-transparent dark:via-violet-400/60"
/>
<div className="relative mx-auto w-full max-w-7xl px-6 py-16 sm:px-8 sm:py-20 lg:px-10">
<motion.div
initial={reduce ? false : { opacity: 0, y: 24 }}
whileInView={reduce ? undefined : { opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="grid gap-12 lg:grid-cols-12"
>
{/* Brand + newsletter */}
<div className="lg:col-span-4">
<a
href="#top"
className="inline-flex items-center gap-3 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 dark:focus-visible:ring-offset-slate-950"
>
<span className="grid h-10 w-10 place-items-center rounded-xl bg-gradient-to-br from-indigo-500 via-violet-500 to-sky-500 shadow-lg shadow-indigo-500/30">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-6 w-6 text-white">
<path
d="M12 2v6m0 8v6m10-10h-6M8 12H2m14.95-7.07-4.24 4.24m-1.42 1.42-4.24 4.24m0-9.9 4.24 4.24m1.42 1.42 4.24 4.24"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
</span>
<span className="footx-shine bg-gradient-to-r from-indigo-600 via-violet-600 to-sky-600 bg-clip-text text-2xl font-semibold tracking-tight text-transparent dark:from-indigo-300 dark:via-violet-300 dark:to-sky-300">
Beacon
</span>
</a>
<p className="mt-5 max-w-sm text-sm leading-relaxed text-slate-600 dark:text-slate-400">
Uptime monitoring, on-call alerts, and branded status pages for teams
who ship every day. Watching 41,200+ endpoints across five continents.
</p>
<form
onSubmit={handleSubscribe}
noValidate
className="mt-7 max-w-sm"
aria-describedby={status !== "idle" ? emailMsgId : undefined}
>
<label
htmlFor={emailId}
className="block text-xs font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400"
>
Monthly incident report
</label>
<div className="mt-2 flex flex-col gap-2 sm:flex-row">
<input
ref={inputRef}
id={emailId}
type="email"
name="email"
autoComplete="email"
inputMode="email"
placeholder="you@company.com"
value={email}
aria-invalid={status === "invalid"}
aria-describedby={status !== "idle" ? emailMsgId : undefined}
onChange={(e) => {
setEmail(e.target.value);
if (status !== "idle") setStatus("idle");
}}
className="w-full rounded-lg border border-slate-300/80 bg-white/80 px-3.5 py-2.5 text-sm text-slate-900 placeholder:text-slate-400 shadow-sm backdrop-blur transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 aria-[invalid=true]:border-rose-400 dark:border-white/10 dark:bg-white/5 dark:text-white dark:placeholder:text-slate-500 dark:focus-visible:ring-offset-slate-950"
/>
<button
type="submit"
className="shrink-0 rounded-lg bg-gradient-to-r from-indigo-500 to-violet-500 px-4 py-2.5 text-sm font-semibold text-white shadow-md shadow-indigo-500/25 transition hover:from-indigo-600 hover:to-violet-600 active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 dark:focus-visible:ring-offset-slate-950"
>
Subscribe
</button>
</div>
<p
id={emailMsgId}
role="status"
aria-live="polite"
className={`mt-2 min-h-[1.25rem] text-xs ${
status === "invalid"
? "text-rose-600 dark:text-rose-400"
: status === "success"
? "text-emerald-600 dark:text-emerald-400"
: "text-slate-500 dark:text-slate-500"
}`}
>
{status === "invalid"
? "Please enter a valid email address."
: status === "success"
? "You're in — the next report lands on the 1st. No spam."
: "One email a month. Real incidents, honest postmortems."}
</p>
</form>
<ul className="mt-7 flex items-center gap-2">
{SOCIALS.map((social) => (
<li key={social.name}>
<a
href={social.href}
target="_blank"
rel="noopener"
aria-label={social.name}
className="grid h-10 w-10 place-items-center rounded-lg border border-slate-300/70 bg-white/60 text-slate-600 shadow-sm backdrop-blur transition hover:-translate-y-0.5 hover:border-indigo-400 hover:text-indigo-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 dark:border-white/10 dark:bg-white/5 dark:text-slate-300 dark:hover:border-violet-400 dark:hover:text-violet-300 dark:focus-visible:ring-offset-slate-950"
>
{social.icon}
</a>
</li>
))}
</ul>
</div>
{/* Link columns */}
<nav
aria-label="Footer"
className="grid grid-cols-1 gap-x-8 gap-y-2 sm:grid-cols-2 lg:col-span-8 lg:grid-cols-4 lg:gap-y-0"
>
{COLUMNS.map((col) => {
const panelId = `${baseId}-panel-${col.id}`;
const isOpen = !!open[col.id];
return (
<div key={col.id} className="border-b border-slate-300/60 py-2 sm:border-none sm:py-0 dark:border-white/10">
<h3>
<button
type="button"
onClick={() => toggleColumn(col.id)}
aria-expanded={isOpen}
aria-controls={panelId}
className="flex w-full items-center justify-between gap-2 py-2 text-left text-sm font-semibold text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 sm:cursor-default sm:py-0 dark:text-white dark:focus-visible:ring-offset-slate-950"
>
<span>{col.title}</span>
<svg
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
className={`h-4 w-4 shrink-0 text-slate-500 transition-transform duration-200 sm:hidden ${
isOpen ? "rotate-180" : ""
}`}
>
<path d="m6 9 6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</h3>
<ul
id={panelId}
className={`${isOpen ? "grid" : "hidden"} gap-1 pb-2 sm:grid sm:pb-0 sm:pt-4`}
>
{col.links.map((link) => (
<li key={link.label}>
<a
href={link.href}
className="inline-block rounded py-1.5 text-sm text-slate-600 transition hover:text-indigo-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 dark:text-slate-400 dark:hover:text-violet-300 dark:focus-visible:ring-offset-slate-950"
>
{link.label}
</a>
</li>
))}
</ul>
</div>
);
})}
</nav>
</motion.div>
{/* Bottom bar */}
<div className="mt-14 flex flex-col gap-6 border-t border-slate-300/60 pt-8 dark:border-white/10 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-wrap items-center gap-x-6 gap-y-3">
<span className="inline-flex items-center gap-2 rounded-full border border-emerald-500/30 bg-emerald-500/10 px-3 py-1 text-xs font-medium text-emerald-700 dark:text-emerald-300">
<span className="relative flex h-2 w-2">
<span className="footx-ring absolute inline-flex h-full w-full rounded-full bg-emerald-500" aria-hidden="true" />
<span className="footx-dot relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
</span>
All systems operational
</span>
<div className="flex items-center gap-2">
<label
htmlFor={`${baseId}-region`}
className="text-xs font-medium text-slate-500 dark:text-slate-400"
>
Region
</label>
<select
id={`${baseId}-region`}
value={region}
onChange={(e) => setRegion(e.target.value)}
className="rounded-lg border border-slate-300/70 bg-white/70 px-2.5 py-1.5 text-xs font-medium text-slate-700 shadow-sm backdrop-blur transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:focus-visible:ring-offset-slate-950"
>
{REGIONS.map((r) => (
<option key={r.href} value={r.href} className="text-slate-900">
{r.label}
</option>
))}
</select>
</div>
</div>
<div className="flex items-center gap-5">
<p className="text-xs text-slate-500 dark:text-slate-500">
© {new Date().getFullYear()} Beacon Systems, Inc. Built in the open.
</p>
<button
type="button"
onClick={scrollTop}
className="inline-flex items-center gap-1.5 rounded-lg border border-slate-300/70 bg-white/60 px-3 py-1.5 text-xs font-medium text-slate-700 shadow-sm backdrop-blur transition hover:-translate-y-0.5 hover:border-indigo-400 hover:text-indigo-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-100 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:border-violet-400 dark:hover:text-violet-200 dark:focus-visible:ring-offset-slate-950"
>
Back to top
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-3.5 w-3.5">
<path d="M12 19V5m0 0-6 6m6-6 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Multi Column Footer With Newsletter
OriginalA brand block with an inline email newsletter signup sits beside three navigation columns, over a bottom bar with copyright and social icons.

Minimal Single Row Footer
OriginalA compact single-row footer that centres a wordmark, inline link list and social icons above a slim copyright line.

Dark Footer With Oversized Brand
OriginalA dark footer pairing four compact link columns with an oversized gradient brand wordmark and a live status indicator.

Sitemap Style Footer
OriginalA five-column sitemap-style footer with a brand intro and contact CTA above a full site index and a legal bottom bar.

Columns Footer
Originalmulti-column footer

Newsletter Footer
Originalfooter with newsletter

Minimal Footer
Originalminimal centred footer

Dark Footer
Originaldark footer with columns

CTA Top Footer
Originalfooter with a CTA on top

Social Footer
Originalfooter with social icons

App Links Footer
Originalfooter with app store links

Sitemap Footer
Originallarge sitemap footer

