App Links Footer
Original · freefooter with app store links
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-app-links.json"use client";
import { useId, useState } from "react";
import type { ChangeEvent, FormEvent, KeyboardEvent } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type PlatformId = "ios" | "android";
type PlatformInfo = {
id: PlatformId;
tabLabel: string;
requirement: string;
size: string;
rating: string;
reviews: string;
version: string;
age: string;
};
const PLATFORMS: PlatformInfo[] = [
{
id: "ios",
tabLabel: "iPhone & iPad",
requirement: "Requires iOS 16.4 or later. Optimised for iPhone 12 and newer.",
size: "52.4 MB",
rating: "4.9",
reviews: "24,610",
version: "3.8.1",
age: "4+",
},
{
id: "android",
tabLabel: "Android",
requirement: "Requires Android 11 or later. Works offline with sync on reconnect.",
size: "38.9 MB",
rating: "4.8",
reviews: "18,142",
version: "3.8.0",
age: "Everyone",
},
];
const COLUMNS: { title: string; links: string[] }[] = [
{ title: "Product", links: ["Features", "Light meter", "Plant ID", "Care reminders", "What's new"] },
{ title: "Company", links: ["About us", "Careers", "Press kit", "Sustainability", "Field notes"] },
{ title: "Support", links: ["Help centre", "Community", "Contact us", "System status", "Report a bug"] },
{ title: "Legal", links: ["Privacy", "Terms", "Cookie settings", "Open source"] },
];
const REGIONS: { value: string; label: string }[] = [
{ value: "us", label: "United States — USD" },
{ value: "gb", label: "United Kingdom — GBP" },
{ value: "eu", label: "Eurozone — EUR" },
{ value: "in", label: "India — INR" },
{ value: "au", label: "Australia — AUD" },
];
const SOCIALS: { label: string; href: string; path: string }[] = [
{
label: "Verdant on Instagram",
href: "https://instagram.com/",
path: "instagram",
},
{
label: "Verdant on X",
href: "https://x.com/",
path: "x",
},
{
label: "Verdant on YouTube",
href: "https://youtube.com/",
path: "youtube",
},
];
function SocialGlyph({ name }: { name: string }) {
if (name === "instagram") {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} className="h-5 w-5" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="5.2" />
<circle cx="12" cy="12" r="3.8" />
<circle cx="17.4" cy="6.6" r="1" fill="currentColor" stroke="none" />
</svg>
);
}
if (name === "x") {
return (
<svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5" aria-hidden="true">
<path d="M17.53 3h3.02l-6.6 7.54L21.75 21h-6.08l-4.76-6.22L5.46 21H2.44l7.06-8.07L2.25 3h6.24l4.3 5.69L17.53 3zm-1.06 16.2h1.67L7.6 4.7H5.8l10.67 14.5z" />
</svg>
);
}
return (
<svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5" aria-hidden="true">
<path d="M23.5 6.2a3 3 0 0 0-2.11-2.12C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.39.58A3 3 0 0 0 .5 6.2 31.4 31.4 0 0 0 0 12a31.4 31.4 0 0 0 .5 5.8 3 3 0 0 0 2.11 2.12C4.5 20.5 12 20.5 12 20.5s7.5 0 9.39-.58a3 3 0 0 0 2.11-2.12A31.4 31.4 0 0 0 24 12a31.4 31.4 0 0 0-.5-5.8zM9.6 15.5v-7l6.2 3.5-6.2 3.5z" />
</svg>
);
}
function AppStoreButton({ active }: { active: boolean }) {
return (
<a
href="https://apps.apple.com/"
target="_blank"
rel="noopener"
aria-label="Download Verdant on the Apple App Store"
className={[
"group inline-flex flex-1 items-center gap-3 rounded-2xl px-4 py-3 transition",
"bg-neutral-900 text-white hover:bg-neutral-800",
"dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950",
active ? "ring-2 ring-emerald-500 ring-offset-2 ring-offset-white dark:ring-offset-neutral-950" : "",
].join(" ")}
>
<svg viewBox="0 0 24 24" fill="currentColor" className="h-7 w-7 shrink-0" aria-hidden="true">
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 8.73 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.2 4.08M12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25" />
</svg>
<span className="text-left leading-tight">
<span className="block text-[0.62rem] font-medium uppercase tracking-wide opacity-70">Download on the</span>
<span className="block text-base font-semibold">App Store</span>
</span>
</a>
);
}
function GooglePlayButton({ active }: { active: boolean }) {
return (
<a
href="https://play.google.com/"
target="_blank"
rel="noopener"
aria-label="Get Verdant on Google Play"
className={[
"group inline-flex flex-1 items-center gap-3 rounded-2xl px-4 py-3 transition",
"bg-neutral-900 text-white hover:bg-neutral-800",
"dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950",
active ? "ring-2 ring-emerald-500 ring-offset-2 ring-offset-white dark:ring-offset-neutral-950" : "",
].join(" ")}
>
<svg viewBox="0 0 24 24" className="h-7 w-7 shrink-0" aria-hidden="true">
<path d="M3.6 2.3a1 1 0 0 0-.5.87v17.66a1 1 0 0 0 .5.87l9.66-9.7L3.6 2.3z" fill="currentColor" opacity="0.85" />
<path d="M17.3 8.16 5.1 1.35a1 1 0 0 0-.98-.02l9.7 9.74 3.48-2.91z" fill="currentColor" opacity="0.55" />
<path d="M17.3 15.84 13.82 12.93l-9.7 9.74a1 1 0 0 0 .98-.02l12.2-6.81z" fill="currentColor" opacity="0.7" />
<path d="M21.4 11.13 17.3 8.16l-3.48 2.77 3.48 2.91 4.1-2.29a1 1 0 0 0 0-1.42z" fill="currentColor" />
</svg>
<span className="text-left leading-tight">
<span className="block text-[0.62rem] font-medium uppercase tracking-wide opacity-70">Get it on</span>
<span className="block text-base font-semibold">Google Play</span>
</span>
</a>
);
}
export default function FootxAppLinks() {
const prefersReduced = useReducedMotion();
const reduce = prefersReduced ?? false;
const uid = useId();
const emailId = `${uid}-email`;
const errId = `${uid}-err`;
const regionId = `${uid}-region`;
const tabId = (id: PlatformId) => `${uid}-tab-${id}`;
const panelId = (id: PlatformId) => `${uid}-panel-${id}`;
const [active, setActive] = useState<PlatformId>("ios");
const [email, setEmail] = useState("");
const [status, setStatus] = useState<"idle" | "error" | "success">("idle");
const [sentTo, setSentTo] = useState("");
const [region, setRegion] = useState("us");
const order: PlatformId[] = PLATFORMS.map((p) => p.id);
const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
const idx = order.indexOf(active);
let next = idx;
if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (idx + 1) % order.length;
else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (idx - 1 + order.length) % order.length;
else if (event.key === "Home") next = 0;
else if (event.key === "End") next = order.length - 1;
else return;
event.preventDefault();
const target = order[next];
setActive(target);
const el = document.getElementById(tabId(target));
if (el) el.focus();
};
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const value = email.trim();
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value);
if (!valid) {
setStatus("error");
return;
}
setSentTo(value);
setStatus("success");
setEmail("");
};
const onEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
setEmail(event.target.value);
if (status === "error") setStatus("idle");
};
const onRegionChange = (event: ChangeEvent<HTMLSelectElement>) => {
setRegion(event.target.value);
};
const backToTop = () => {
if (typeof window !== "undefined") {
window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
}
};
const cardMotion = reduce
? {}
: {
initial: { opacity: 0, y: 24 },
whileInView: { opacity: 1, y: 0 },
viewport: { once: true, margin: "-80px" },
transition: { duration: 0.6, ease: "easeOut" as const },
};
const swap = reduce
? { initial: { opacity: 1 }, animate: { opacity: 1 }, exit: { opacity: 1 } }
: {
initial: { opacity: 0, y: 8 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -8 },
transition: { duration: 0.28, ease: "easeOut" as const },
};
const activeRegion = REGIONS.find((r) => r.value === region)?.label ?? REGIONS[0].label;
const ringField =
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-neutral-950";
const linkRing =
"rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-50 dark:focus-visible:ring-offset-neutral-950";
return (
<section
className="relative w-full overflow-hidden bg-neutral-50 text-neutral-700 dark:bg-neutral-950 dark:text-neutral-300"
aria-labelledby={`${uid}-footer-title`}
>
<style>{`
@keyframes footx-al-aurora {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); opacity: 0.45; }
50% { transform: translate3d(3%, -5%, 0) scale(1.12); opacity: 0.7; }
}
@keyframes footx-al-drift {
0%, 100% { transform: translateY(0) rotate(-4deg); }
50% { transform: translateY(-12px) rotate(6deg); }
}
.footx-al-aurora { animation: footx-al-aurora 16s ease-in-out infinite; }
.footx-al-aurora--slow { animation-duration: 22s; animation-direction: reverse; }
.footx-al-leaf { animation: footx-al-drift 9s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.footx-al-aurora, .footx-al-leaf { animation: none !important; }
}
`}</style>
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="footx-al-aurora absolute -left-24 top-[-6rem] h-72 w-72 rounded-full bg-emerald-400/25 blur-3xl dark:bg-emerald-500/20" />
<div className="footx-al-aurora footx-al-aurora--slow absolute right-[-6rem] bottom-[-4rem] h-80 w-80 rounded-full bg-sky-400/20 blur-3xl dark:bg-emerald-400/10" />
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-emerald-500/40 to-transparent" />
</div>
<div className="relative mx-auto max-w-6xl px-6 py-20 sm:px-8 lg:py-24">
<h2 id={`${uid}-footer-title`} className="sr-only">
Verdant footer and app downloads
</h2>
<motion.div
{...cardMotion}
className="relative overflow-hidden rounded-3xl border border-neutral-200 bg-white/80 p-6 shadow-sm backdrop-blur-sm sm:p-10 dark:border-neutral-800 dark:bg-neutral-900/60"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
className="footx-al-leaf pointer-events-none absolute right-6 top-6 h-10 w-10 text-emerald-500/50"
fill="currentColor"
>
<path d="M20 3c-8 0-14 4-15.5 11C3 17 3 20 3 21c1-3 3-6 7-8-2 3-3 5-3 7 6-1 11-5 12-11 .6-3 1-5 1-6z" />
</svg>
<div className="grid gap-10 lg:grid-cols-[1.15fr_1fr] lg:gap-14">
<div>
<div className="flex items-center gap-3">
<span className="grid h-11 w-11 place-items-center rounded-xl bg-emerald-500 text-white shadow-sm">
<svg viewBox="0 0 24 24" fill="currentColor" className="h-6 w-6" aria-hidden="true">
<path d="M20 3c-8 0-14 4-15.5 11C3 17 3 20 3 21c1-3 3-6 7-8-2 3-3 5-3 7 6-1 11-5 12-11 .6-3 1-5 1-6z" />
</svg>
</span>
<span className="text-lg font-semibold tracking-tight text-neutral-900 dark:text-white">Verdant</span>
</div>
<h3 className="mt-6 text-2xl font-semibold tracking-tight text-neutral-900 sm:text-3xl dark:text-white">
Never miss a watering again.
</h3>
<p className="mt-3 max-w-md text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
A built-in light meter, smart watering schedules, and 12,000+ species in your pocket.
Verdant turns forgetful plant parents into people with a jungle.
</p>
<div className="mt-7">
<div
role="tablist"
aria-label="Choose your platform"
className="inline-flex rounded-xl border border-neutral-200 bg-neutral-100 p-1 dark:border-neutral-700 dark:bg-neutral-800/70"
>
{PLATFORMS.map((p) => {
const selected = active === p.id;
return (
<button
key={p.id}
id={tabId(p.id)}
type="button"
role="tab"
aria-selected={selected}
aria-controls={panelId(p.id)}
tabIndex={selected ? 0 : -1}
onClick={() => setActive(p.id)}
onKeyDown={onTabKeyDown}
className={[
"rounded-lg px-4 py-2 text-sm font-medium transition",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-100 dark:focus-visible:ring-offset-neutral-800",
selected
? "bg-white text-neutral-900 shadow-sm dark:bg-neutral-950 dark:text-white"
: "text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200",
].join(" ")}
>
{p.tabLabel}
</button>
);
})}
</div>
{PLATFORMS.map((p) => {
const hidden = active !== p.id;
return (
<div
key={p.id}
id={panelId(p.id)}
role="tabpanel"
aria-labelledby={tabId(p.id)}
hidden={hidden}
className="mt-5"
>
<AnimatePresence mode="wait" initial={false}>
{!hidden && (
<motion.div key={p.id} {...swap}>
<dl className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
<dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
Rating
</dt>
<dd className="mt-0.5 flex items-center gap-1 text-sm font-semibold text-neutral-900 dark:text-white">
<svg viewBox="0 0 24 24" fill="currentColor" className="h-3.5 w-3.5 text-amber-500" aria-hidden="true">
<path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14l-5-4.87 6.91-1.01L12 2z" />
</svg>
{p.rating}
<span className="ml-1 text-[0.68rem] font-normal text-neutral-500 dark:text-neutral-500">
({p.reviews})
</span>
</dd>
</div>
<div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
<dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
Size
</dt>
<dd className="mt-0.5 text-sm font-semibold text-neutral-900 dark:text-white">{p.size}</dd>
</div>
<div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
<dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
Version
</dt>
<dd className="mt-0.5 text-sm font-semibold text-neutral-900 dark:text-white">{p.version}</dd>
</div>
<div className="rounded-xl border border-neutral-200 bg-neutral-50 px-3 py-2.5 dark:border-neutral-800 dark:bg-neutral-950/60">
<dt className="text-[0.68rem] font-medium uppercase tracking-wide text-neutral-500 dark:text-neutral-500">
Age
</dt>
<dd className="mt-0.5 text-sm font-semibold text-neutral-900 dark:text-white">{p.age}</dd>
</div>
</dl>
<p className="mt-3 text-xs leading-relaxed text-neutral-500 dark:text-neutral-500">{p.requirement}</p>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
<div className="mt-5 flex flex-col gap-3 sm:flex-row">
<AppStoreButton active={active === "ios"} />
<GooglePlayButton active={active === "android"} />
</div>
</div>
</div>
<div className="rounded-2xl border border-neutral-200 bg-neutral-50/80 p-6 dark:border-neutral-800 dark:bg-neutral-950/50">
<AnimatePresence mode="wait" initial={false}>
{status === "success" ? (
<motion.div key="ok" {...swap}>
<span className="grid h-12 w-12 place-items-center rounded-full bg-emerald-500/15 text-emerald-600 dark:text-emerald-400">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" className="h-6 w-6" aria-hidden="true">
<path d="m5 13 4 4L19 7" />
</svg>
</span>
<h4 className="mt-4 text-lg font-semibold text-neutral-900 dark:text-white">Check your inbox</h4>
<p className="mt-2 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
We sent a download link to <span className="font-medium text-neutral-900 dark:text-white">{sentTo}</span>. Open it on your phone to install Verdant.
</p>
<button
type="button"
onClick={() => setStatus("idle")}
className={`mt-4 text-sm font-semibold text-emerald-600 underline-offset-4 hover:underline dark:text-emerald-400 ${ringField} rounded`}
>
Use a different email
</button>
</motion.div>
) : (
<motion.form key="form" {...swap} onSubmit={onSubmit} noValidate>
<h4 className="text-lg font-semibold text-neutral-900 dark:text-white">Email me the app</h4>
<p className="mt-2 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
Prefer not to search the store? Drop your email and we'll send a one-tap install link.
</p>
<label htmlFor={emailId} className="mt-5 block text-sm font-medium text-neutral-800 dark:text-neutral-200">
Email address
</label>
<div className="mt-2 flex flex-col gap-2 sm:flex-row">
<input
id={emailId}
name="email"
type="email"
inputMode="email"
autoComplete="email"
placeholder="you@example.com"
value={email}
onChange={onEmailChange}
aria-invalid={status === "error"}
aria-describedby={status === "error" ? errId : undefined}
className={[
"w-full rounded-xl border bg-white px-4 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-400 dark:bg-neutral-900 dark:text-white dark:placeholder:text-neutral-500",
status === "error"
? "border-rose-500 dark:border-rose-500"
: "border-neutral-300 dark:border-neutral-700",
ringField,
].join(" ")}
/>
<button
type="submit"
className={`shrink-0 rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-emerald-500 dark:bg-emerald-500 dark:hover:bg-emerald-400 ${ringField}`}
>
Send link
</button>
</div>
<div id={errId} role="alert" aria-live="polite" className="min-h-[1.25rem]">
{status === "error" && (
<p className="mt-2 text-xs font-medium text-rose-600 dark:text-rose-400">
Please enter a valid email address so we can reach you.
</p>
)}
</div>
<p className="mt-3 text-xs leading-relaxed text-neutral-500 dark:text-neutral-500">
One message, no marketing. See our{" "}
<a href="#privacy" className={`font-medium text-neutral-700 underline-offset-2 hover:underline dark:text-neutral-300 ${linkRing}`}>
privacy notice
</a>
.
</p>
</motion.form>
)}
</AnimatePresence>
</div>
</div>
</motion.div>
<div className="mt-16 grid grid-cols-2 gap-8 sm:grid-cols-3 lg:grid-cols-5">
<div className="col-span-2 sm:col-span-3 lg:col-span-1">
<p className="max-w-xs text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
Verdant is built by a small team who kept killing their houseplants. Now the app does the remembering.
</p>
</div>
{COLUMNS.map((col) => (
<nav key={col.title} aria-label={col.title}>
<h3 className="text-xs font-semibold uppercase tracking-wider text-neutral-900 dark:text-white">{col.title}</h3>
<ul className="mt-4 space-y-2.5">
{col.links.map((link) => (
<li key={link}>
<a
href={`#${link.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`}
className={`text-sm text-neutral-600 transition hover:text-emerald-600 dark:text-neutral-400 dark:hover:text-emerald-400 ${linkRing}`}
>
{link}
</a>
</li>
))}
</ul>
</nav>
))}
</div>
<div className="mt-14 flex flex-col gap-6 border-t border-neutral-200 pt-8 dark:border-neutral-800 md:flex-row md:items-center md:justify-between">
<div className="flex flex-wrap items-center gap-x-5 gap-y-3">
<p className="text-sm text-neutral-500 dark:text-neutral-500">
© 2026 Verdant Labs, Inc. All rights reserved.
</p>
<div className="flex items-center gap-3">
{SOCIALS.map((s) => (
<a
key={s.label}
href={s.href}
target="_blank"
rel="noopener"
aria-label={s.label}
className={`grid h-9 w-9 place-items-center rounded-full border border-neutral-200 text-neutral-600 transition hover:border-emerald-500 hover:text-emerald-600 dark:border-neutral-800 dark:text-neutral-400 dark:hover:border-emerald-500 dark:hover:text-emerald-400 ${linkRing}`}
>
<SocialGlyph name={s.path} />
</a>
))}
</div>
</div>
<div className="flex flex-wrap items-center gap-4">
<div className="flex items-center gap-2">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} className="h-4 w-4 text-neutral-500 dark:text-neutral-500" aria-hidden="true">
<circle cx="12" cy="12" r="9" />
<path d="M3 12h18M12 3c2.5 2.6 3.8 5.7 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.7-3.8-9S9.5 5.6 12 3z" />
</svg>
<label htmlFor={regionId} className="sr-only">
Choose your region and currency
</label>
<select
id={regionId}
value={region}
onChange={onRegionChange}
className={`rounded-lg border border-neutral-300 bg-white px-3 py-1.5 text-sm text-neutral-700 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300 ${ringField}`}
>
{REGIONS.map((r) => (
<option key={r.value} value={r.value}>
{r.label}
</option>
))}
</select>
</div>
<button
type="button"
onClick={backToTop}
className={`inline-flex items-center gap-1.5 text-sm font-medium text-neutral-600 transition hover:text-emerald-600 dark:text-neutral-400 dark:hover:text-emerald-400 ${linkRing}`}
>
Back to top
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4" aria-hidden="true">
<path d="M12 19V5M5 12l7-7 7 7" />
</svg>
</button>
</div>
</div>
<p className="mt-6 text-xs text-neutral-400 dark:text-neutral-600">
Prices and availability shown for {activeRegion}. Apple and the Apple logo are trademarks of Apple Inc. Google Play is a trademark of Google LLC.
</p>
</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

Sitemap Footer
Originallarge sitemap footer

Gradient Footer
Originalgradient footer

