Customizable footer that shows up when the user scrolls to the bottom of the page
interface FooterLinkItem {
external?: boolean;
href: string;
icon?: ReactNode;
label: string;
}
const legalLinks: FooterLinkItem[] = [
{
href: "#",
label: "Privacy Policy",
},
{
href: "#",
label: "Terms of Service",
},
{
href: "#",
label: "Cookie Policy",
},
];
const exploreLinks: FooterLinkItem[] = [
{ href: "#", label: "Home" },
{ href: "#", label: "Docs" },
{ href: "#", label: "Tools" },
{ href: "#", label: "Pricing" },
];
function FooterLink({ external, href, label, icon = null }: FooterLinkItem) {
return (
<Link
className="inline-flex items-center gap-2 font-medium font-sans text-background text-base leading-snug transition-transform will-change-transform hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-background/50 focus-visible:ring-offset-2 focus-visible:ring-offset-foreground motion-reduce:transition-none motion-reduce:hover:translate-y-0"
href={href}
{...(external ? { rel: "noopener noreferrer", target: "_blank" } : {})}
>
{icon}
{label}
</Link>
);
}
function FooterColumn({
children,
title,
}: {
children: ReactNode;
title: string;
}) {
return (
<div className="flex min-w-0 flex-col">
<h2 className="mb-4 font-normal font-sans text-background/50 text-sm">
{title}
</h2>
{children}
</div>
);
}
function FooterLinkList({ links }: { links: FooterLinkItem[] }) {
return (
<ul className="flex flex-col gap-1.5">
{links.map((link) => (
<li key={`${link.href}-${link.label}`}>
<FooterLink {...link} />
</li>
))}
</ul>
);
}
const Page = () => {
return (
<main>
<section className="h-screen">Section 1 </section>
<ShowUpFooter className="h-104 bg-[#101010] md:h-80" >
<div className="mx-auto grid h-full w-full max-w-6xl grid-cols-2 items-start gap-x-16 gap-y-10 p-12 md:grid-cols-4">
<Link
aria-label="Springr home"
className="inline-flex w-fit focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-background/50 focus-visible:ring-offset-2 focus-visible:ring-offset-foreground"
href="/"
>
<SpringrLogoWithText
className="fill-background md:text-2xl"
iconClassName="md:size-10"
/>
</Link>
<FooterColumn title="Explore">
<FooterLinkList links={exploreLinks} />
</FooterColumn>
<FooterColumn title="Legal">
<FooterLinkList links={legalLinks} />
</FooterColumn>
<FooterColumn title="Contact">
<FooterLink
href="#"
icon={<MailIcon className="size-4" />}
label="Email us"
/>
<FooterLink
href="#"
icon={<PhoneCallIcon className="size-4" />}
label="Phone"
/>
<p className="mt-8 font-medium font-sans text-background text-base leading-snug">
Random Street, <br />
101010, Random City, Italy
</p>
</FooterColumn>
</div>
</ShowUpFooter>
<Magnet className="group absolute right-4 bottom-4 aspect-square rounded-full border-2 border-muted p-2 transition-[colors] hover:right-6 hover:bottom-6 hover:border-transparent hover:p-0 md:right-8 md:bottom-8 md:hover:right-10 md:hover:bottom-10">
<ArrowUp
className="cursor-pointer text-muted transition-all group-hover:-rotate-45 group-hover:scale-150 group-hover:text-accent"
onClick={handleScrollToTop}
/>
</Magnet>
</main>
)
}
export default Page;Install the following dependencies:
npm install motionbun add motiondeno add motionpnpm add motionyarn add motionCopy and paste the following code into your project.
"use client";
import {
type MotionProps,
motion,
useScroll,
useTransform,
} from "motion/react";
import type React from "react";
import type { RefObject } from "react";
import { cn } from "@/lib/utils";
function ShowUpFooter({
scrollContainer,
className,
children,
...props
}: {
scrollContainer?: RefObject<HTMLElement | null>;
} & MotionProps &
React.ComponentProps<"footer">) {
const { scrollYProgress } = useScroll({
container: scrollContainer,
});
const y = useTransform(scrollYProgress, [0, 1], ["-100%", "0%"]);
const clipPath = useTransform(
scrollYProgress,
[0, 1],
["inset(100% 0 0 0)", "inset(0% 0 0 0)"]
);
return (
<motion.footer
className={cn("h-104 w-full", className)}
style={{ clipPath, y }}
{...props}
>
{children}
</motion.footer>
);
}
export { ShowUpFooter };Place it at the bottom of the page, as a normal footer. You can then pass whatever you want inside the component.
import { ShowUpFooter } from "@/components/show-up-footer";
<main>
<section>Section 1 </section>
<section> Section 2 </section>
<ShowUpFooter>
// Your footer content here
</ShowUpFooter>
</main>It accepts all the props of the footer element, plus the following:
| Prop | Type | Default | Description |
|---|---|---|---|
| scrollContainer | RefObject<HTMLElement | null> | - | The container to listen to the scroll event. If not provided, the window will be used. |