Footer 2

Rounded-top footer with a brand block beside a multi-column link grid. Each column caps at five links and shows a "View all" arrow when there are more. A soft radial tint sits behind the top edge, and a bottom meta row carries the copyright and secondary links.

Preview

Installation

npx shadcn@latest add https://hirael.com/r/footer-02.json

Code

components/blocks/footer-02.tsx
"use client";

import * as React from "react";
import { ArrowRight } from "lucide-react";

import { cn } from "@/lib/utils";

type FooterColumn = {
  title: string;
  href: string;
  links: readonly { label: string; href: string }[];
};

const MAX_LINKS = 5;

const COLUMNS: readonly FooterColumn[] = [
  {
    title: "Product",
    href: "#",
    links: [
      { label: "Overview", href: "#" },
      { label: "Components", href: "#" },
      { label: "Templates", href: "#" },
      { label: "Pricing", href: "#" },
      { label: "Changelog", href: "#" },
      { label: "Roadmap", href: "#" },
    ],
  },
  {
    title: "Resources",
    href: "#",
    links: [
      { label: "Documentation", href: "#" },
      { label: "Guides", href: "#" },
      { label: "Tutorials", href: "#" },
      { label: "API reference", href: "#" },
      { label: "Examples", href: "#" },
    ],
  },
  {
    title: "Company",
    href: "#",
    links: [
      { label: "About", href: "#" },
      { label: "Blog", href: "#" },
      { label: "Careers", href: "#" },
      { label: "Contact", href: "#" },
      { label: "Privacy", href: "#" },
    ],
  },
];

function BrandMark({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 64 64"
      fill="none"
      stroke="currentColor"
      strokeWidth="2.4"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden
      className={className}
    >
      <rect x="10" y="10" width="44" height="44" rx="12" />
      <path d="M24 40 V24 l8 10 8 -10 V40" />
    </svg>
  );
}

export default function Footer02() {
  return (
    <footer
      data-slot="footer"
      className="relative mx-2 rounded-t-3xl border-t border-border bg-background xl:mx-4"
    >
      <div
        data-slot="footer-inner"
        className="relative container w-full py-12 lg:py-16"
      >
        <div
          aria-hidden
          className="pointer-events-none absolute inset-x-0 top-0 h-32"
          style={{
            background:
              "radial-gradient(35% 128px at 50% 0%, color-mix(in oklch, var(--primary) 14%, transparent), transparent)",
          }}
        />

        <div className="relative flex flex-col gap-10 lg:flex-row">
          <div
            data-slot="footer-brand"
            className="flex items-start gap-4 lg:w-1/3"
          >
            <span className="inline-flex size-12 shrink-0 items-center justify-center rounded-xl border border-border bg-card text-foreground">
              <BrandMark className="size-6" />
            </span>
            <div className="space-y-3">
              <span className="inline-flex items-center font-mono text-sm font-semibold tracking-[-0.02em] text-foreground">
                Hirael
              </span>
              <p className="max-w-xs text-sm leading-relaxed text-muted-foreground">
                Notes on building for the web — interface patterns, small tools,
                and resources worth keeping around.
              </p>
            </div>
          </div>

          <nav
            data-slot="footer-nav"
            aria-label="Footer"
            className="grid flex-1 grid-cols-2 gap-x-8 gap-y-8 sm:grid-cols-3"
          >
            {COLUMNS.map((col) => (
              <div data-slot="footer-column" key={col.title}>
                <a
                  href={col.href}
                  className="text-sm font-semibold text-foreground transition-colors hover:text-foreground"
                >
                  {col.title}
                </a>
                <ul className="mt-3 space-y-2.5">
                  {col.links.slice(0, MAX_LINKS).map((link) => (
                    <li key={link.label}>
                      <a
                        href={link.href}
                        className="text-sm text-muted-foreground transition-colors hover:text-foreground"
                      >
                        {link.label}
                      </a>
                    </li>
                  ))}
                  {col.links.length > MAX_LINKS && (
                    <li>
                      <a
                        href={col.href}
                        className="group inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
                      >
                        View all
                        <ArrowRight className="size-3.5 transition-transform duration-150 ease-out group-hover:translate-x-0.5 rtl:rotate-180 rtl:group-hover:-translate-x-0.5" />
                      </a>
                    </li>
                  )}
                </ul>
              </div>
            ))}
          </nav>
        </div>

        <div
          data-slot="footer-meta"
          className={cn(
            "relative mt-10 flex flex-col items-start justify-between gap-3 border-t border-border pt-6",
            "sm:flex-row sm:items-center",
          )}
        >
          <p className="font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground">
            © 2026 · All rights reserved
          </p>
          <div className="flex items-center gap-4">
            <a
              href="#"
              className="text-xs text-muted-foreground transition-colors hover:text-foreground"
            >
              Terms
            </a>
            <a
              href="#"
              className="text-xs text-muted-foreground transition-colors hover:text-foreground"
            >
              Privacy
            </a>
            <a
              href="#"
              className="text-xs text-muted-foreground transition-colors hover:text-foreground"
            >
              Status
            </a>
          </div>
        </div>
      </div>
    </footer>
  );
}

Dependencies

npm

lucide-react