Dark Footer
Original · freedark footer with columns
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-dark.json"use client";
import { useState, useId, useEffect, type FormEvent, type ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
type SubscribeStatus = "idle" | "error" | "success";
type FooterLink = { label: string; href: string; badge?: string };
type FooterColumn = { title: string; links: FooterLink[] };
const COLUMNS: FooterColumn[] = [
{
title: "Product",
links: [
{ label: "Pipelines", href: "#pipelines" },
{ label: "Preview Deploys", href: "#preview" },
{ label: "Observability", href: "#observability" },
{ label: "Secrets Vault", href: "#secrets" },
{ label: "Edge Cache", href: "#edge" },
{ label: "Changelog", href: "#changelog", badge: "New" },
],
},
{
title: "Developers",
links: [
{ label: "Documentation", href: "#docs" },
{ label: "API Reference", href: "#api" },
{ label: "CLI Guide", href: "#cli" },
{ label: "System Status", href: "#status" },
{ label: "Open Source", href: "#oss" },
{ label: "Community Forum", href: "#community" },
],
},
{
title: "Company",
links: [
{ label: "About Northloop", href: "#about" },
{ label: "Careers", href: "#careers", badge: "12 open" },
{ label: "Engineering Blog", href: "#blog" },
{ label: "Customers", href: "#customers" },
{ label: "Press Kit", href: "#press" },
{ label: "Contact Sales", href: "#contact" },
],
},
{
title: "Legal",
links: [
{ label: "Privacy Policy", href: "#privacy" },
{ label: "Terms of Service", href: "#terms" },
{ label: "Security & Trust", href: "#security" },
{ label: "Data Processing", href: "#dpa" },
{ label: "Cookie Settings", href: "#cookies" },
{ label: "Sub-processors", href: "#subprocessors" },
],
},
];
const SOCIALS: { label: string; href: string; path: ReactNode }[] = [
{
label: "Northloop on GitHub",
href: "https://github.com",
path: (
<path d="M12 2C6.48 2 2 6.58 2 12.25c0 4.53 2.87 8.37 6.84 9.73.5.1.68-.22.68-.49 0-.24-.01-.87-.01-1.71-2.78.62-3.37-1.37-3.37-1.37-.45-1.18-1.11-1.5-1.11-1.5-.91-.64.07-.62.07-.62 1 .07 1.53 1.06 1.53 1.06.89 1.56 2.34 1.11 2.91.85.09-.66.35-1.11.63-1.37-2.22-.26-4.55-1.14-4.55-5.06 0-1.12.39-2.03 1.03-2.75-.1-.26-.45-1.3.1-2.71 0 0 .84-.28 2.75 1.05a9.36 9.36 0 0 1 5 0c1.91-1.33 2.75-1.05 2.75-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.63 1.03 2.75 0 3.93-2.34 4.8-4.57 5.05.36.32.68.94.68 1.9 0 1.37-.01 2.48-.01 2.82 0 .27.18.6.69.49A10.02 10.02 0 0 0 22 12.25C22 6.58 17.52 2 12 2Z" />
),
},
{
label: "Northloop on X",
href: "https://x.com",
path: (
<path d="M17.53 3H20.5l-6.5 7.43L21.75 21h-5.98l-4.69-6.13L5.7 21H2.72l6.96-7.95L2.25 3h6.13l4.24 5.6L17.53 3Zm-1.05 16.2h1.65L7.6 4.71H5.83l10.65 14.49Z" />
),
},
{
label: "Northloop on LinkedIn",
href: "https://linkedin.com",
path: (
<path d="M4.98 3.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5ZM3 9h4v12H3V9Zm7 0h3.83v1.64h.05c.53-1 1.84-2.05 3.79-2.05 4.05 0 4.8 2.67 4.8 6.14V21H18.5v-5.62c0-1.34-.02-3.06-1.87-3.06-1.87 0-2.16 1.46-2.16 2.96V21H10.5V9Z" />
),
},
{
label: "Northloop on YouTube",
href: "https://youtube.com",
path: (
<path d="M23.5 6.9a3 3 0 0 0-2.11-2.12C19.5 4.25 12 4.25 12 4.25s-7.5 0-9.39.53A3 3 0 0 0 .5 6.9 31.3 31.3 0 0 0 0 12a31.3 31.3 0 0 0 .5 5.1 3 3 0 0 0 2.11 2.12c1.89.53 9.39.53 9.39.53s7.5 0 9.39-.53a3 3 0 0 0 2.11-2.12A31.3 31.3 0 0 0 24 12a31.3 31.3 0 0 0-.5-5.1ZM9.6 15.5v-7l6.2 3.5-6.2 3.5Z" />
),
},
{
label: "Northloop on Discord",
href: "https://discord.com",
path: (
<path d="M20.32 4.57A19.8 19.8 0 0 0 15.4 3.1a.07.07 0 0 0-.08.04c-.21.38-.45.87-.61 1.26a18.3 18.3 0 0 0-5.42 0c-.16-.4-.4-.88-.62-1.26a.08.08 0 0 0-.08-.04 19.7 19.7 0 0 0-4.9 1.47.07.07 0 0 0-.04.03C.6 9.24-.36 13.78.12 18.27a.08.08 0 0 0 .03.05 19.9 19.9 0 0 0 5.99 3 .08.08 0 0 0 .08-.03c.46-.63.87-1.29 1.22-1.99a.08.08 0 0 0-.04-.11c-.65-.24-1.27-.54-1.87-.88a.08.08 0 0 1-.01-.13l.37-.29a.07.07 0 0 1 .08-.01c3.92 1.79 8.16 1.79 12.03 0a.07.07 0 0 1 .08.01l.37.29a.08.08 0 0 1-.01.13c-.6.35-1.22.64-1.87.88a.08.08 0 0 0-.04.11c.36.7.77 1.36 1.22 1.99a.08.08 0 0 0 .08.03 19.8 19.8 0 0 0 6-3 .08.08 0 0 0 .03-.05c.57-5.2-.95-9.7-4.02-13.67a.06.06 0 0 0-.03-.03ZM8.02 15.53c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.09 2.16 2.42 0 1.34-.96 2.42-2.16 2.42Zm7.97 0c-1.18 0-2.16-1.08-2.16-2.42s.96-2.42 2.16-2.42c1.21 0 2.18 1.09 2.16 2.42 0 1.34-.95 2.42-2.16 2.42Z" />
),
},
];
const REGIONS = [
"United States (English)",
"United Kingdom (English)",
"Deutschland (Deutsch)",
"France (Français)",
"日本 (日本語)",
"Brasil (Português)",
];
export default function FootxDark() {
const reduce = useReducedMotion();
const emailId = useId();
const errorId = useId();
const regionId = useId();
const headingId = useId();
const panelBaseId = useId();
const [email, setEmail] = useState("");
const [submittedEmail, setSubmittedEmail] = useState("");
const [status, setStatus] = useState<SubscribeStatus>("idle");
const [openCol, setOpenCol] = useState<number | null>(null);
const [region, setRegion] = useState(REGIONS[0]);
const [isDesktop, setIsDesktop] = useState(false);
useEffect(() => {
const mq = window.matchMedia("(min-width: 768px)");
const sync = () => setIsDesktop(mq.matches);
sync();
mq.addEventListener("change", sync);
return () => mq.removeEventListener("change", sync);
}, []);
function handleSubscribe(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const value = email.trim();
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
if (!valid) {
setStatus("error");
return;
}
setSubmittedEmail(value);
setStatus("success");
setEmail("");
}
function resetSubscribe() {
setStatus("idle");
setSubmittedEmail("");
}
function scrollToTop() {
window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
}
const rise = reduce
? {}
: {
initial: { opacity: 0, y: 18 },
whileInView: { opacity: 1, y: 0 },
viewport: { once: true, margin: "-80px" },
transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] as const },
};
return (
<section
className="relative w-full overflow-hidden bg-gradient-to-b from-slate-900 to-slate-950 text-slate-400 dark:from-zinc-950 dark:to-black"
aria-labelledby={headingId}
>
<style>{`
@keyframes footx-sheen { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@keyframes footx-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }
@keyframes footx-pop { 0% { opacity: 0; transform: scale(0) rotate(-14deg); } 60% { transform: scale(1.15) rotate(2deg); } 100% { opacity: 1; transform: scale(1) rotate(0); } }
.footx-sheen { animation: footx-sheen 7s linear infinite; }
.footx-dot { animation: footx-pulse 2.4s ease-in-out infinite; }
.footx-check { animation: footx-pop .5s cubic-bezier(.34,1.56,.64,1) both; }
.footx-collapsible { transition: grid-template-rows .32s ease; }
@media (prefers-reduced-motion: reduce) {
.footx-sheen, .footx-dot, .footx-check { animation: none !important; }
.footx-collapsible { transition: none !important; }
}
`}</style>
{/* animated hairline */}
<div
aria-hidden="true"
className="footx-sheen absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-indigo-400/70 to-transparent bg-[length:200%_100%]"
/>
{/* ambient glow */}
<div
aria-hidden="true"
className="pointer-events-none absolute -top-24 left-1/2 h-64 w-[42rem] max-w-full -translate-x-1/2 rounded-full bg-indigo-500/10 blur-3xl dark:bg-violet-500/10"
/>
<h2 id={headingId} className="sr-only">
Northloop site footer
</h2>
<div className="relative mx-auto max-w-7xl px-6 py-16 sm:px-8 sm:py-20">
{/* Brand + newsletter */}
<motion.div
{...rise}
className="grid gap-12 border-b border-white/10 pb-12 lg:grid-cols-[1.35fr_1fr] lg:gap-16"
>
<div className="max-w-md">
<a
href="#top"
className="inline-flex items-center gap-2.5 rounded-lg text-slate-50 outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
aria-label="Northloop home"
>
<svg
viewBox="0 0 32 32"
className="h-8 w-8"
fill="none"
aria-hidden="true"
>
<rect width="32" height="32" rx="9" className="fill-indigo-500" />
<path
d="M9 22V10l7 8V10m0 12 7-8v8"
stroke="white"
strokeWidth="2.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className="text-lg font-semibold tracking-tight">
Northloop
</span>
</a>
<p className="mt-5 text-sm leading-relaxed text-slate-400">
The deploy platform for teams that ship on Fridays. Build
pipelines, preview every pull request, and watch it all run on the
edge — without babysitting infrastructure.
</p>
<a
href="#status"
className="mt-6 inline-flex items-center gap-2 rounded-full border border-emerald-400/30 bg-emerald-400/10 px-3 py-1.5 text-xs font-medium text-emerald-300 outline-none transition-colors hover:border-emerald-400/50 hover:bg-emerald-400/15 focus-visible:ring-2 focus-visible:ring-emerald-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
>
<span className="footx-dot h-2 w-2 rounded-full bg-emerald-400" />
All systems operational
</a>
<ul className="mt-7 flex flex-wrap gap-2.5">
{SOCIALS.map((social) => (
<li key={social.label}>
<a
href={social.href}
target="_blank"
rel="noopener"
aria-label={social.label}
className="group inline-flex h-10 w-10 items-center justify-center rounded-xl border border-white/10 bg-white/5 text-slate-400 outline-none transition-colors hover:border-white/20 hover:bg-white/10 hover:text-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
>
<svg
viewBox="0 0 24 24"
className="h-5 w-5 fill-current"
aria-hidden="true"
>
{social.path}
</svg>
</a>
</li>
))}
</ul>
</div>
{/* Newsletter */}
<div className="lg:pl-8">
<h3 className="text-sm font-semibold text-slate-100">
Ship Notes — monthly
</h3>
<p className="mt-2 text-sm text-slate-400">
One email a month: new releases, edge benchmarks, and a build
teardown from a real customer. No fluff, unsubscribe anytime.
</p>
{status === "success" ? (
<div
role="status"
className="mt-5 flex items-start gap-3 rounded-2xl border border-emerald-400/25 bg-emerald-400/10 p-4"
>
<span className="footx-check mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-emerald-400 text-slate-950">
<svg
viewBox="0 0 24 24"
className="h-4 w-4"
fill="none"
aria-hidden="true"
>
<path
d="m5 12 4.5 4.5L19 7"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</span>
<div className="min-w-0">
<p className="text-sm font-medium text-emerald-200">
You're on the list.
</p>
<p className="mt-1 break-words text-sm text-emerald-300/80">
The next issue is headed to {submittedEmail}.
</p>
<button
type="button"
onClick={resetSubscribe}
className="mt-2 rounded text-sm font-medium text-emerald-200 underline decoration-emerald-400/40 underline-offset-4 outline-none transition-colors hover:text-white focus-visible:ring-2 focus-visible:ring-emerald-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
>
Use a different email
</button>
</div>
</div>
) : (
<form onSubmit={handleSubscribe} noValidate className="mt-5">
<label
htmlFor={emailId}
className="block text-xs font-medium text-slate-300"
>
Work email
</label>
<div className="mt-2 flex flex-col gap-2.5 sm:flex-row">
<input
id={emailId}
type="email"
name="email"
inputMode="email"
autoComplete="email"
placeholder="you@company.com"
value={email}
onChange={(event) => {
setEmail(event.target.value);
if (status === "error") setStatus("idle");
}}
aria-invalid={status === "error"}
aria-describedby={status === "error" ? errorId : undefined}
className="w-full rounded-xl border border-white/10 bg-white/5 px-4 py-2.5 text-sm text-slate-100 placeholder:text-slate-500 outline-none transition-colors focus-visible:border-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 aria-[invalid=true]:border-rose-400/70 aria-[invalid=true]:ring-1 aria-[invalid=true]:ring-rose-400/40 dark:focus-visible:ring-offset-black"
/>
<button
type="submit"
className="shrink-0 rounded-xl bg-indigo-500 px-5 py-2.5 text-sm font-semibold text-white outline-none transition-colors hover:bg-indigo-400 focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
>
Subscribe
</button>
</div>
<p
id={errorId}
role="alert"
aria-live="polite"
className="mt-2 min-h-[1.25rem] text-xs text-rose-400"
>
{status === "error"
? "Please enter a valid email address."
: ""}
</p>
</form>
)}
</div>
</motion.div>
{/* Link columns */}
<motion.nav
{...rise}
aria-label="Footer"
className="grid grid-cols-1 gap-x-8 py-4 sm:grid-cols-2 md:grid-cols-4 md:py-10"
>
{COLUMNS.map((column, index) => {
const expanded = isDesktop || openCol === index;
const panelId = `${panelBaseId}-${index}`;
return (
<div
key={column.title}
className="border-b border-white/10 py-2 md:border-0 md:py-0"
>
<h3>
<button
type="button"
onClick={() =>
setOpenCol((current) =>
current === index ? null : index,
)
}
aria-expanded={expanded}
aria-controls={panelId}
className="flex w-full items-center justify-between gap-2 rounded-md py-3 text-left text-sm font-semibold text-slate-100 outline-none transition-colors hover:text-white focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 md:cursor-default md:py-0 md:pb-4 dark:focus-visible:ring-offset-black"
>
{column.title}
<svg
viewBox="0 0 24 24"
className={`h-4 w-4 text-slate-400 transition-transform duration-300 md:hidden ${
expanded ? "rotate-180" : ""
}`}
fill="none"
aria-hidden="true"
>
<path
d="m6 9 6 6 6-6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</h3>
<div
id={panelId}
className={`footx-collapsible grid md:grid-rows-[1fr] ${
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
}`}
>
<div className="overflow-hidden">
<ul className="space-y-3 pb-4 md:pb-0">
{column.links.map((link) => (
<li key={link.label}>
<a
href={link.href}
className="group inline-flex items-center gap-2 rounded text-sm text-slate-400 outline-none transition-colors hover:text-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
>
<span className="underline decoration-transparent decoration-1 underline-offset-4 transition-colors group-hover:decoration-indigo-400">
{link.label}
</span>
{link.badge ? (
<span className="rounded-full border border-indigo-400/30 bg-indigo-400/10 px-1.5 py-0.5 text-[10px] font-medium leading-none text-indigo-300">
{link.badge}
</span>
) : null}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
);
})}
</motion.nav>
{/* Bottom bar */}
<div className="mt-6 flex flex-col gap-6 border-t border-white/10 pt-8 md:flex-row md:items-center md:justify-between">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-6">
<p className="text-xs text-slate-500">
© {new Date().getFullYear()} Northloop, Inc. All rights reserved.
</p>
<div className="flex items-center gap-2">
<label htmlFor={regionId} className="sr-only">
Region and language
</label>
<svg
viewBox="0 0 24 24"
className="h-4 w-4 text-slate-500"
fill="none"
aria-hidden="true"
>
<circle
cx="12"
cy="12"
r="9"
stroke="currentColor"
strokeWidth="1.6"
/>
<path
d="M3 12h18M12 3c2.5 2.5 2.5 15 0 18M12 3c-2.5 2.5-2.5 15 0 18"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
/>
</svg>
<select
id={regionId}
value={region}
onChange={(event) => setRegion(event.target.value)}
className="rounded-lg border border-white/10 bg-white/5 py-1.5 pl-2.5 pr-8 text-xs text-slate-300 outline-none transition-colors hover:border-white/20 focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 dark:focus-visible:ring-offset-black"
>
{REGIONS.map((option) => (
<option
key={option}
value={option}
className="bg-slate-900 text-slate-100"
>
{option}
</option>
))}
</select>
</div>
</div>
<button
type="button"
onClick={scrollToTop}
className="inline-flex items-center gap-2 self-start rounded-full border border-white/10 bg-white/5 px-4 py-2 text-xs font-medium text-slate-300 outline-none transition-colors hover:border-white/20 hover:text-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 md:self-auto dark:focus-visible:ring-offset-black"
>
Back to top
<svg
viewBox="0 0 24 24"
className="h-3.5 w-3.5"
fill="none"
aria-hidden="true"
>
<path
d="M12 19V5m0 0-6 6m6-6 6 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</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

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

Gradient Footer
Originalgradient footer

