Skip to content
All Taste components

Navbar

Navbar — mega menu

A top bar whose Product link opens a wide panel of grouped pages with a featured card.

open state · 2 link groups with icons · featured card · footer strip · page dimmed behind

"use client";
import { useEffect, useRef, useState } from "react";
import { ArrowRight, BarChart3, ChevronDown, CreditCard, FileText, Mail, Repeat, Users, type LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";

type MenuLink = { icon: LucideIcon; title: string; text: string; href: string };
const groups: { label: string; links: MenuLink[] }[] = [
  {
    label: "Get paid",
    links: [
      { icon: FileText, title: "Invoices", text: "Branded invoices in under a minute.", href: "/invoices" },
      { icon: Repeat, title: "Recurring billing", text: "Retainers that bill themselves.", href: "/recurring" },
      { icon: CreditCard, title: "Online payments", text: "Card and bank transfer on every invoice.", href: "/payments" },
    ],
  },
  {
    label: "Run the business",
    links: [
      { icon: Mail, title: "Reminders", text: "Polite follow-ups, sent for you.", href: "/reminders" },
      { icon: BarChart3, title: "Reports", text: "Cash flow, late payers, tax totals.", href: "/reports" },
      { icon: Users, title: "Client portal", text: "Every invoice and receipt, one link.", href: "/portal" },
    ],
  },
];
const links = [
  { label: "Pricing", href: "/pricing" },
  { label: "Customers", href: "/customers" },
  { label: "Resources", href: "/resources" },
];

export function NavbarMegaMenu() {
  const [open, setOpen] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  // Close on Escape and on a click outside the header.
  useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);
    const onClick = (e: MouseEvent) => { if (!ref.current?.contains(e.target as Node)) setOpen(false); };
    document.addEventListener("keydown", onKey);
    document.addEventListener("mousedown", onClick);
    return () => { document.removeEventListener("keydown", onKey); document.removeEventListener("mousedown", onClick); };
  }, [open]);

  return (
    <div ref={ref} className="relative z-40 font-sans">
      <header className="border-b border-border bg-bg">
        <div className="mx-auto flex h-16 max-w-6xl items-center gap-6 px-4 sm:px-7">
          <a href="/" className="flex items-center gap-2 text-base font-semibold tracking-tight text-fg">
            <span aria-hidden className="size-6 rounded-sm bg-primary" />
            Fernbill
          </a>
          <nav aria-label="Main" className="hidden md:block">
            <ul className="flex items-center gap-0.5">
              <li>
                <button
                  type="button"
                  aria-expanded={open}
                  aria-controls="product-menu"
                  onClick={() => setOpen(!open)}
                  className="inline-flex h-9 items-center gap-1 rounded px-3 text-sm text-fg-muted transition hover:bg-surface hover:text-fg aria-expanded:bg-surface aria-expanded:text-fg"
                >
                  Product <ChevronDown className={cn("size-3.5 transition-transform", open && "rotate-180")} aria-hidden />
                </button>
              </li>
              {links.map((l) => (
                <li key={l.href}>
                  <a href={l.href} className="inline-flex h-9 items-center rounded px-3 text-sm text-fg-muted transition hover:bg-surface hover:text-fg">{l.label}</a>
                </li>
              ))}
            </ul>
          </nav>
          <div className="ml-auto flex items-center gap-2">
            <a href="/login" className="hidden h-9 items-center rounded px-3 text-sm font-medium text-fg hover:bg-surface-2 sm:inline-flex">Log in</a>
            <a href="/signup" className="inline-flex h-9 items-center rounded bg-primary px-3.5 text-sm font-medium text-primary-fg transition hover:bg-primary-hover">Start free</a>
          </div>
        </div>
      </header>

      {open && (
        <div id="product-menu" className="absolute inset-x-0 top-full border-b border-border bg-bg shadow-xl">
          <div className="mx-auto grid max-w-6xl gap-4 p-4 md:grid-cols-[1fr_1fr_0.9fr] md:gap-7 md:px-7 md:py-7">
            {groups.map((g) => (
              <div key={g.label}>
                <p className="mb-1.5 ml-2.5 text-[11px] font-medium uppercase tracking-[0.12em] text-fg-subtle">{g.label}</p>
                {g.links.map(({ icon: Icon, ...l }) => (
                  <a key={l.href} href={l.href} className="flex gap-3 rounded-lg p-2.5 transition hover:bg-surface">
                    <span className="grid size-9 shrink-0 place-items-center rounded border border-border bg-bg text-fg shadow-sm">
                      <Icon className="size-[18px]" aria-hidden />
                    </span>
                    <span>
                      <span className="block text-sm font-medium text-fg">{l.title}</span>
                      <span className="block text-[13px] leading-snug text-fg-muted">{l.text}</span>
                    </span>
                  </a>
                ))}
              </div>
            ))}
            <a href="/changelog/currencies" className="flex min-h-[200px] flex-col justify-end gap-2.5 rounded-lg border border-border bg-surface bg-[radial-gradient(120%_90%_at_100%_0%,color-mix(in_srgb,var(--tst-primary)_30%,transparent),transparent_60%)] p-5">
              <span className="w-fit rounded-full bg-primary-soft px-2 py-0.5 text-xs font-medium text-primary-soft-fg">New</span>
              <span className="text-base font-semibold tracking-tight text-fg">Get paid in 24 currencies</span>
              <span className="text-[13px] leading-relaxed text-fg-muted">Bill clients abroad in their money and receive yours, with the rate shown up front.</span>
              <span className="inline-flex items-center gap-1 text-sm font-medium text-primary">Read the update <ArrowRight className="size-3.5" aria-hidden /></span>
            </a>
          </div>
          <div className="mx-auto flex max-w-6xl flex-wrap justify-between gap-2 border-t border-border px-4 py-3.5 text-[13px] text-fg-muted md:px-9">
            <span>New here? <a href="/tour" className="font-medium text-fg">Take the two-minute tour</a></span>
            <span>Questions before you start? <a href="/contact" className="font-medium text-fg">Talk to us</a></span>
          </div>
        </div>
      )}
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.