Fazekit

Code

"use client";
import * as React from "react";
import { motion, useReducedMotion, useScroll, useSpring } from "motion/react";
import { ArrowUp, Mail, MessagesSquare, Rss, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

export interface FooterMinimalProps {
  brand?: string;
  tagline?: string;
  links?: { label: string; href?: string }[];
  socials?: { label: string; href?: string; icon: LucideIcon }[];
  copyright?: string;
  backToTopLabel?: string;
  /** Override what "back to top" does (default: smooth-scroll the window). */
  onBackToTop?: () => void;
  className?: string;
}

export function FooterMinimal({
  brand = "Lumen",
  tagline = "Calm software for busy teams.",
  links = [
    { label: "Product", href: "#" },
    { label: "Pricing", href: "#" },
    { label: "Blog", href: "#" },
    { label: "Careers", href: "#" },
    { label: "Contact", href: "#" },
  ],
  socials = [
    { label: "Community", icon: MessagesSquare, href: "#" },
    { label: "RSS feed", icon: Rss, href: "#" },
    { label: "Email", icon: Mail, href: "#" },
  ],
  copyright = "© 2026 Lumen Labs. All rights reserved.",
  backToTopLabel = "Back to top",
  onBackToTop,
  className,
}: FooterMinimalProps) {
  const reduce = useReducedMotion();
  const { scrollYProgress } = useScroll();
  const progress = useSpring(scrollYProgress, { stiffness: 200, damping: 30 });

  const toTop = () => {
    if (onBackToTop) return onBackToTop();
    window.scrollTo({ top: 0, behavior: reduce ? "auto" : "smooth" });
  };

  return (
    <footer className={cn("w-full border-t bg-background text-foreground", className)}>
      <div className="mx-auto max-w-6xl px-5 sm:px-8">
        <div className="flex flex-col gap-8 py-10 md:flex-row md:items-center md:justify-between">
          <div className="flex items-center gap-3">
            <span aria-hidden className="grid size-9 place-items-center rounded-xl bg-linear-to-br from-amber-400 via-orange-500 to-rose-500 shadow-sm">
              <span className="size-3.5 rounded-full bg-white/90" />
            </span>
            <div>
              <p className="font-semibold tracking-tight">{brand}</p>
              <p className="text-sm text-muted-foreground">{tagline}</p>
            </div>
          </div>

          <nav aria-label="Footer">
            <ul className="flex flex-wrap gap-x-7 gap-y-3">
              {links.map((l) => (
                <li key={l.label}>
                  <UnderlineLink href={l.href}>{l.label}</UnderlineLink>
                </li>
              ))}
            </ul>
          </nav>

          <button
            type="button"
            onClick={toTop}
            className="group inline-flex w-fit items-center gap-3 rounded-full py-1 pl-1 pr-4 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          >
            <span className="relative grid size-10 place-items-center">
              <svg viewBox="0 0 40 40" className="absolute inset-0 -rotate-90" aria-hidden>
                <circle cx="20" cy="20" r="18.5" fill="none" stroke="var(--color-border)" strokeWidth="1.5" />
                <motion.circle
                  cx="20"
                  cy="20"
                  r="18.5"
                  fill="none"
                  stroke="var(--color-foreground)"
                  strokeWidth="1.5"
                  strokeLinecap="round"
                  style={{ pathLength: progress }}
                />
              </svg>
              <span className="relative block h-4 w-4 overflow-hidden" aria-hidden>
                <ArrowUp className="absolute inset-0 size-4 transition-transform duration-300 ease-out group-hover:-translate-y-5" />
                <ArrowUp className="absolute inset-0 size-4 translate-y-5 transition-transform duration-300 ease-out group-hover:translate-y-0" />
              </span>
            </span>
            {backToTopLabel}
          </button>
        </div>

        <div className="flex flex-col-reverse gap-4 border-t py-6 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
          <p>{copyright}</p>
          <ul className="flex items-center gap-1">
            {socials.map((s) => (
              <li key={s.label}>
                <a
                  href={s.href ?? "#"}
                  aria-label={s.label}
                  title={s.label}
                  className="grid size-9 place-items-center rounded-full transition hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  <s.icon className="size-4" aria-hidden />
                </a>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </footer>
  );
}

function UnderlineLink({ href, children }: { href?: string; children: React.ReactNode }) {
  return (
    <a
      href={href ?? "#"}
      className="group relative inline-block py-1 text-sm font-medium text-muted-foreground transition-colors rounded-sm hover:text-foreground focus-visible:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background"
    >
      {children}
      <span
        aria-hidden
        className="absolute inset-x-0 bottom-0 h-px origin-right scale-x-0 bg-current transition-transform duration-300 ease-out group-hover:origin-left group-hover:scale-x-100 group-focus-visible:origin-left group-focus-visible:scale-x-100"
      />
    </a>
  );
}

More in Footers

View all →