Web InnoventixFreeCode

Dropdown Navbar

Original · free

navbar with hover dropdowns

byWeb InnoventixReact + Tailwind
navxdropdownnavbars
Open

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/navx-dropdown.json
navx-dropdown.tsx
"use client";

import { useEffect, useId, useRef, useState, type ReactElement } from "react";
import { AnimatePresence, motion, useReducedMotion, type Variants } from "motion/react";

/* ---------------------------------- icons --------------------------------- */

type IconProps = { className?: string };
type IconComp = (props: IconProps) => ReactElement;

function Svg({ className, children }: IconProps & { children: ReactElement | ReactElement[] }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.75}
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
      aria-hidden="true"
    >
      {children}
    </svg>
  );
}

const IconInsights: IconComp = (p) => (
  <Svg {...p}>
    <path d="M4 20h16" />
    <path d="M6 20V11" />
    <path d="M12 20V5" />
    <path d="M18 20v-6" />
  </Svg>
);
const IconFlow: IconComp = (p) => (
  <Svg {...p}>
    <circle cx="6" cy="6" r="2.2" />
    <circle cx="6" cy="18" r="2.2" />
    <circle cx="18" cy="12" r="2.2" />
    <path d="M8 6h3a4 4 0 0 1 4 4M8 18h3a4 4 0 0 0 4-4" />
  </Svg>
);
const IconSignal: IconComp = (p) => (
  <Svg {...p}>
    <path d="M3 12h3l2-6 4 12 2-6h7" />
  </Svg>
);
const IconWarehouse: IconComp = (p) => (
  <Svg {...p}>
    <ellipse cx="12" cy="6" rx="7" ry="3" />
    <path d="M5 6v6c0 1.66 3.13 3 7 3s7-1.34 7-3V6" />
    <path d="M5 12v6c0 1.66 3.13 3 7 3s7-1.34 7-3v-6" />
  </Svg>
);
const IconShield: IconComp = (p) => (
  <Svg {...p}>
    <path d="M12 3l7 3v5c0 4.5-3 7.6-7 9-4-1.4-7-4.5-7-9V6l7-3z" />
    <path d="M9 12l2 2 4-4" />
  </Svg>
);
const IconCode: IconComp = (p) => (
  <Svg {...p}>
    <path d="M9 8l-4 4 4 4" />
    <path d="M15 8l4 4-4 4" />
  </Svg>
);
const IconTrend: IconComp = (p) => (
  <Svg {...p}>
    <path d="M3 17l6-6 4 4 8-8" />
    <path d="M17 7h4v4" />
  </Svg>
);
const IconSupport: IconComp = (p) => (
  <Svg {...p}>
    <path d="M4 13v-1a8 8 0 0 1 16 0v1" />
    <rect x="3" y="13" width="4" height="6" rx="1.5" />
    <rect x="17" y="13" width="4" height="6" rx="1.5" />
    <path d="M20 19a4 4 0 0 1-4 4h-2" />
  </Svg>
);
const IconRocket: IconComp = (p) => (
  <Svg {...p}>
    <path d="M12 3c3.2 1.2 5 4.2 5 8l-2.5 3h-5L7 11c0-3.8 1.8-6.8 5-8z" />
    <circle cx="12" cy="9.5" r="1.4" />
    <path d="M9.5 17l-2 4M14.5 17l2 4" />
  </Svg>
);
const IconBuilding: IconComp = (p) => (
  <Svg {...p}>
    <rect x="5" y="3" width="14" height="18" rx="1.5" />
    <path d="M9 7h2M13 7h2M9 11h2M13 11h2" />
    <path d="M9 21v-3h6v3" />
  </Svg>
);
const IconPackage: IconComp = (p) => (
  <Svg {...p}>
    <path d="M12 3l8 4v10l-8 4-8-4V7l8-4z" />
    <path d="M4 7l8 4 8-4" />
    <path d="M12 11v10" />
  </Svg>
);
const IconChangelog: IconComp = (p) => (
  <Svg {...p}>
    <path d="M8 6h12M8 12h12M8 18h12" />
    <circle cx="4" cy="6" r="1" />
    <circle cx="4" cy="12" r="1" />
    <circle cx="4" cy="18" r="1" />
  </Svg>
);
const IconStatus: IconComp = (p) => (
  <Svg {...p}>
    <circle cx="12" cy="12" r="9" />
    <path d="M7 12h2l1.6-3 2 6 1.4-3H17" />
  </Svg>
);
const IconInfo: IconComp = (p) => (
  <Svg {...p}>
    <circle cx="12" cy="12" r="9" />
    <path d="M12 11v5" />
    <path d="M12 8h.01" />
  </Svg>
);
const IconBriefcase: IconComp = (p) => (
  <Svg {...p}>
    <rect x="3" y="7" width="18" height="13" rx="2" />
    <path d="M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" />
    <path d="M3 12h18" />
  </Svg>
);
const IconBlog: IconComp = (p) => (
  <Svg {...p}>
    <path d="M4 20h4l10-10a2.83 2.83 0 0 0-4-4L4 16v4z" />
    <path d="M13 6l4 4" />
  </Svg>
);
const IconUsers: IconComp = (p) => (
  <Svg {...p}>
    <circle cx="9" cy="8" r="3" />
    <path d="M3 20a6 6 0 0 1 12 0" />
    <path d="M16 5.2a3 3 0 0 1 0 5.6" />
    <path d="M18 14a6 6 0 0 1 3 5" />
  </Svg>
);
const IconChevron: IconComp = (p) => (
  <Svg {...p}>
    <path d="M6 9l6 6 6-6" />
  </Svg>
);
const IconMenu: IconComp = (p) => (
  <Svg {...p}>
    <path d="M4 7h16M4 12h16M4 17h16" />
  </Svg>
);
const IconClose: IconComp = (p) => (
  <Svg {...p}>
    <path d="M6 6l12 12M18 6L6 18" />
  </Svg>
);
const IconArrow: IconComp = (p) => (
  <Svg {...p}>
    <path d="M5 12h14M13 6l6 6-6 6" />
  </Svg>
);

/* ------------------------------- nav content ------------------------------ */

type MenuLink = { label: string; desc: string; href: string; icon: IconComp; badge?: string };
type MenuColumn = { heading: string; links: MenuLink[] };
type Featured = { badge: string; title: string; desc: string; href: string; cta: string };
type NavItem = {
  id: string;
  label: string;
  href?: string;
  columns?: MenuColumn[];
  featured?: Featured;
  width?: string;
  align?: "left" | "right";
};

const NAV: NavItem[] = [
  {
    id: "product",
    label: "Product",
    width: "w-[43rem]",
    align: "left",
    featured: {
      badge: "New",
      title: "Signal 2.0 is live",
      desc: "Anomaly detection that flags revenue dips three times faster than manual alerts.",
      href: "#signal",
      cta: "Read the release notes",
    },
    columns: [
      {
        heading: "Analytics",
        links: [
          { label: "Insights Engine", desc: "Live dashboards across every workspace", href: "#insights", icon: IconInsights },
          { label: "Funnel Explorer", desc: "See where users drop off, step by step", href: "#funnels", icon: IconFlow },
          { label: "Signal", desc: "Anomaly alerts before customers notice", href: "#signal", icon: IconSignal },
        ],
      },
      {
        heading: "Automation",
        links: [
          { label: "Flow Builder", desc: "Automate handoffs without writing code", href: "#flows", icon: IconFlow },
          { label: "Warehouse Sync", desc: "Two-way sync with Snowflake and BigQuery", href: "#warehouse", icon: IconWarehouse },
          { label: "Access Control", desc: "SCIM provisioning and full audit logs", href: "#access", icon: IconShield },
        ],
      },
    ],
  },
  {
    id: "solutions",
    label: "Solutions",
    width: "w-[36rem]",
    align: "left",
    columns: [
      {
        heading: "By team",
        links: [
          { label: "Engineering", desc: "Track deploys and error budgets", href: "#engineering", icon: IconCode },
          { label: "Revenue", desc: "Forecast pipeline with live data", href: "#revenue", icon: IconTrend },
          { label: "Support", desc: "Cut median resolution time in half", href: "#support", icon: IconSupport },
        ],
      },
      {
        heading: "By company size",
        links: [
          { label: "Startups", desc: "Free for teams under ten people", href: "#startups", icon: IconRocket },
          { label: "Enterprise", desc: "SSO, SLAs, and a dedicated engineer", href: "#enterprise", icon: IconBuilding },
        ],
      },
    ],
  },
  {
    id: "developers",
    label: "Developers",
    width: "w-[21rem]",
    align: "left",
    columns: [
      {
        heading: "Build with Sundial",
        links: [
          { label: "API Reference", desc: "REST and GraphQL endpoints", href: "#api", icon: IconCode },
          { label: "SDKs & Libraries", desc: "TypeScript, Python, Go, and Ruby", href: "#sdks", icon: IconPackage },
          { label: "Changelog", desc: "Every shipped update, weekly", href: "#changelog", icon: IconChangelog },
          { label: "System Status", desc: "99.99% uptime, updated live", href: "#status", icon: IconStatus },
        ],
      },
    ],
  },
  { id: "pricing", label: "Pricing", href: "#pricing" },
  {
    id: "company",
    label: "Company",
    width: "w-[21rem]",
    align: "right",
    columns: [
      {
        heading: "About Sundial",
        links: [
          { label: "Our story", desc: "Why we set out to kill the spreadsheet", href: "#about", icon: IconInfo },
          { label: "Careers", desc: "Twelve open roles across four teams", href: "#careers", icon: IconBriefcase, badge: "Hiring" },
          { label: "Blog", desc: "Product and engineering field notes", href: "#blog", icon: IconBlog },
          { label: "Customers", desc: "How 4,000 teams grow with Sundial", href: "#customers", icon: IconUsers },
        ],
      },
    ],
  },
];

/* --------------------------------- logo ----------------------------------- */

function Logo({ gradientId }: { gradientId: string }) {
  return (
    <span className="flex items-center gap-2.5">
      <svg viewBox="0 0 32 32" className="h-8 w-8 shrink-0" aria-hidden="true">
        <defs>
          <linearGradient id={gradientId} x1="0" y1="0" x2="1" y2="1">
            <stop offset="0" stopColor="#6366f1" />
            <stop offset="1" stopColor="#8b5cf6" />
          </linearGradient>
        </defs>
        <rect width="32" height="32" rx="9" fill={`url(#${gradientId})`} />
        <circle cx="16" cy="18" r="4" fill="#fff" />
        <g stroke="#fff" strokeWidth="1.7" strokeLinecap="round">
          <path d="M16 18L11 9" />
          <path d="M6 18h2M24 18h2M9 11l1.4 1.4M23 11l-1.4 1.4" opacity="0.85" />
        </g>
      </svg>
      <span className="text-[1.05rem] font-semibold tracking-tight text-slate-900 dark:text-white">
        Sundial
      </span>
    </span>
  );
}

/* ------------------------------- component -------------------------------- */

export default function NavxDropdown() {
  const reduce = useReducedMotion();
  const uid = useId();
  const gradientId = `${uid}-logo`;

  const [openId, setOpenId] = useState<string | null>(null);
  const [mobileOpen, setMobileOpen] = useState(false);
  const [mobileSection, setMobileSection] = useState<string | null>(null);

  const navRef = useRef<HTMLElement | null>(null);
  const panelRef = useRef<HTMLDivElement | null>(null);
  const triggerRefs = useRef<Record<string, HTMLButtonElement | null>>({});
  const closeTimer = useRef<number | null>(null);
  const wantsFocusFirst = useRef(false);

  function openMenu(id: string) {
    if (closeTimer.current !== null) {
      window.clearTimeout(closeTimer.current);
      closeTimer.current = null;
    }
    setOpenId(id);
  }
  function scheduleClose() {
    if (closeTimer.current !== null) window.clearTimeout(closeTimer.current);
    closeTimer.current = window.setTimeout(() => setOpenId(null), 130);
  }

  // Focus the first item in a panel opened via keyboard.
  useEffect(() => {
    if (openId && wantsFocusFirst.current) {
      wantsFocusFirst.current = false;
      const first = panelRef.current?.querySelector<HTMLElement>('a[href], button:not([disabled])');
      first?.focus();
    }
  }, [openId]);

  // Escape closes everything; click outside closes desktop menus.
  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") {
        setOpenId(null);
        setMobileOpen(false);
      }
    }
    function onDown(e: MouseEvent) {
      if (openId && navRef.current && !navRef.current.contains(e.target as Node)) {
        setOpenId(null);
      }
    }
    document.addEventListener("keydown", onKey);
    document.addEventListener("mousedown", onDown);
    return () => {
      document.removeEventListener("keydown", onKey);
      document.removeEventListener("mousedown", onDown);
    };
  }, [openId]);

  useEffect(() => {
    return () => {
      if (closeTimer.current !== null) window.clearTimeout(closeTimer.current);
    };
  }, []);

  const panelVariants: Variants = reduce
    ? { hidden: { opacity: 0 }, visible: { opacity: 1 }, exit: { opacity: 0 } }
    : {
        hidden: { opacity: 0, y: -8, scale: 0.985 },
        visible: { opacity: 1, y: 0, scale: 1 },
        exit: { opacity: 0, y: -6, scale: 0.985 },
      };
  const panelTransition = reduce ? { duration: 0 } : { duration: 0.18, ease: "easeOut" as const };

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes navxPulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.35; transform: scale(0.72); }
        }
        @keyframes navxSheen {
          from { transform: translateX(-130%) skewX(-18deg); }
          to { transform: translateX(240%) skewX(-18deg); }
        }
        @keyframes navxDrift {
          0%, 100% { transform: translate3d(0, 0, 0); }
          50% { transform: translate3d(0, -14px, 0); }
        }
        .navx-dot { animation: navxPulse 2.4s ease-in-out infinite; }
        .navx-cta { position: relative; overflow: hidden; }
        .navx-cta__sheen {
          position: absolute; inset: 0; pointer-events: none;
          background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent);
          transform: translateX(-130%) skewX(-18deg);
        }
        .navx-cta:hover .navx-cta__sheen { animation: navxSheen 0.85s ease forwards; }
        .navx-blob { animation: navxDrift 11s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .navx-dot, .navx-blob { animation: none !important; }
          .navx-cta:hover .navx-cta__sheen { animation: none !important; }
        }
      `}</style>

      {/* atmosphere */}
      <div aria-hidden="true" className="pointer-events-none absolute inset-0">
        <div className="navx-blob absolute -left-24 -top-28 h-80 w-80 rounded-full bg-indigo-300/40 blur-3xl dark:bg-indigo-600/20" />
        <div className="navx-blob absolute right-0 top-40 h-72 w-72 rounded-full bg-violet-300/40 blur-3xl dark:bg-violet-700/20" />
      </div>

      {/* ------------------------------ header ------------------------------ */}
      <header className="sticky top-0 z-40 border-b border-slate-200/70 bg-slate-50/80 backdrop-blur-xl dark:border-slate-800/70 dark:bg-slate-950/70">
        <nav
          ref={navRef}
          aria-label="Primary"
          className="mx-auto flex h-16 max-w-7xl items-center justify-between gap-4 px-4 sm:px-6 lg:px-8"
        >
          <a
            href="#top"
            className="rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-950"
          >
            <Logo gradientId={gradientId} />
            <span className="sr-only">Sundial home</span>
          </a>

          {/* desktop nav */}
          <ul className="hidden items-center gap-1 lg:flex">
            {NAV.map((item) => {
              const isOpen = openId === item.id;
              const panelId = `${uid}-${item.id}-panel`;

              if (!item.columns) {
                return (
                  <li key={item.id}>
                    <a
                      href={item.href ?? "#"}
                      className="rounded-lg px-3 py-2 text-sm font-medium text-slate-700 outline-none transition-colors hover:bg-slate-200/60 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:hover:bg-slate-800/70 dark:hover:text-white"
                    >
                      {item.label}
                    </a>
                  </li>
                );
              }

              return (
                <li
                  key={item.id}
                  className="relative"
                  onMouseEnter={() => openMenu(item.id)}
                  onMouseLeave={scheduleClose}
                  onBlur={(e) => {
                    if (!e.currentTarget.contains(e.relatedTarget as Node)) {
                      setOpenId((cur) => (cur === item.id ? null : cur));
                    }
                  }}
                >
                  <button
                    type="button"
                    ref={(el) => {
                      triggerRefs.current[item.id] = el;
                    }}
                    aria-expanded={isOpen}
                    aria-haspopup="true"
                    aria-controls={panelId}
                    onClick={() => setOpenId((cur) => (cur === item.id ? null : item.id))}
                    onKeyDown={(e) => {
                      if (e.key === "ArrowDown") {
                        e.preventDefault();
                        wantsFocusFirst.current = true;
                        openMenu(item.id);
                      } else if (e.key === "Escape") {
                        setOpenId(null);
                      }
                    }}
                    className={`flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-500 ${
                      isOpen
                        ? "bg-slate-200/70 text-slate-900 dark:bg-slate-800/80 dark:text-white"
                        : "text-slate-700 hover:bg-slate-200/60 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800/70 dark:hover:text-white"
                    }`}
                  >
                    {item.label}
                    <IconChevron
                      className={`h-4 w-4 text-slate-400 transition-transform duration-200 dark:text-slate-500 ${
                        isOpen ? "rotate-180" : ""
                      }`}
                    />
                  </button>

                  <AnimatePresence>
                    {isOpen && (
                      <motion.div
                        key="panel"
                        ref={panelRef}
                        id={panelId}
                        role="region"
                        aria-label={`${item.label} menu`}
                        variants={panelVariants}
                        initial="hidden"
                        animate="visible"
                        exit="exit"
                        transition={panelTransition}
                        onKeyDown={(e) => {
                          if (e.key === "Escape") {
                            setOpenId(null);
                            triggerRefs.current[item.id]?.focus();
                          }
                        }}
                        className={`absolute top-full z-50 pt-3 ${item.align === "right" ? "right-0" : "left-0"}`}
                      >
                        <div
                          className={`${item.width ?? "w-[22rem]"} overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-2xl shadow-slate-900/10 ring-1 ring-black/5 dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/40 dark:ring-white/5`}
                        >
                          <div className="flex">
                            <div className="grid flex-1 gap-6 p-4 sm:grid-cols-2">
                              {item.columns.map((col) => (
                                <div key={col.heading}>
                                  <p className="mb-2 px-2 text-[0.7rem] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">
                                    {col.heading}
                                  </p>
                                  <ul className="space-y-0.5">
                                    {col.links.map((link) => {
                                      const LinkIcon = link.icon;
                                      return (
                                        <li key={link.label}>
                                          <a
                                            href={link.href}
                                            onClick={() => setOpenId(null)}
                                            className="group flex items-start gap-3 rounded-xl p-2.5 outline-none transition-colors hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:hover:bg-slate-800/70"
                                          >
                                            <span className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 ring-1 ring-inset ring-indigo-100 transition-colors group-hover:bg-indigo-100 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20 dark:group-hover:bg-indigo-500/20">
                                              <LinkIcon className="h-5 w-5" />
                                            </span>
                                            <span className="min-w-0">
                                              <span className="flex items-center gap-2">
                                                <span className="text-sm font-semibold text-slate-900 dark:text-white">
                                                  {link.label}
                                                </span>
                                                {link.badge && (
                                                  <span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-[0.62rem] font-semibold uppercase tracking-wide text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300">
                                                    <span className="navx-dot h-1.5 w-1.5 rounded-full bg-emerald-500" />
                                                    {link.badge}
                                                  </span>
                                                )}
                                              </span>
                                              <span className="mt-0.5 block text-[0.8rem] leading-snug text-slate-500 dark:text-slate-400">
                                                {link.desc}
                                              </span>
                                            </span>
                                          </a>
                                        </li>
                                      );
                                    })}
                                  </ul>
                                </div>
                              ))}
                            </div>

                            {item.featured && (
                              <a
                                href={item.featured.href}
                                onClick={() => setOpenId(null)}
                                className="group hidden w-56 shrink-0 flex-col justify-between gap-4 border-l border-slate-200 bg-gradient-to-br from-indigo-50 to-violet-50 p-5 outline-none transition-colors hover:from-indigo-100 hover:to-violet-100 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500 dark:border-slate-800 dark:from-indigo-500/10 dark:to-violet-500/10 dark:hover:from-indigo-500/20 dark:hover:to-violet-500/20 sm:flex"
                              >
                                <div>
                                  <span className="inline-flex items-center rounded-full bg-violet-600 px-2 py-0.5 text-[0.62rem] font-semibold uppercase tracking-wide text-white">
                                    {item.featured.badge}
                                  </span>
                                  <p className="mt-3 text-sm font-semibold text-slate-900 dark:text-white">
                                    {item.featured.title}
                                  </p>
                                  <p className="mt-1 text-[0.8rem] leading-snug text-slate-600 dark:text-slate-300">
                                    {item.featured.desc}
                                  </p>
                                </div>
                                <span className="inline-flex items-center gap-1 text-[0.8rem] font-semibold text-indigo-700 dark:text-indigo-300">
                                  {item.featured.cta}
                                  <IconArrow className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
                                </span>
                              </a>
                            )}
                          </div>
                        </div>
                      </motion.div>
                    )}
                  </AnimatePresence>
                </li>
              );
            })}
          </ul>

          {/* desktop actions */}
          <div className="hidden items-center gap-2 lg:flex">
            <a
              href="#signin"
              className="rounded-lg px-3 py-2 text-sm font-medium text-slate-700 outline-none transition-colors hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:hover:text-white"
            >
              Sign in
            </a>
            <a
              href="#start"
              className="navx-cta inline-flex items-center gap-1.5 rounded-lg bg-indigo-600 px-3.5 py-2 text-sm font-semibold text-white shadow-sm outline-none transition-colors hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-950"
            >
              <span className="navx-cta__sheen" aria-hidden="true" />
              Get started
              <IconArrow className="h-4 w-4" />
            </a>
          </div>

          {/* mobile toggle */}
          <button
            type="button"
            aria-expanded={mobileOpen}
            aria-controls={`${uid}-mobile`}
            aria-label={mobileOpen ? "Close menu" : "Open menu"}
            onClick={() => setMobileOpen((o) => !o)}
            className="inline-flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 outline-none transition-colors hover:bg-slate-200/70 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-200 dark:hover:bg-slate-800/70 lg:hidden"
          >
            {mobileOpen ? <IconClose className="h-5 w-5" /> : <IconMenu className="h-5 w-5" />}
          </button>
        </nav>

        {/* ---------------------------- mobile menu --------------------------- */}
        <AnimatePresence>
          {mobileOpen && (
            <motion.div
              key="mobile"
              id={`${uid}-mobile`}
              initial={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}
              animate={reduce ? { opacity: 1 } : { opacity: 1, height: "auto" }}
              exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}
              transition={reduce ? { duration: 0 } : { duration: 0.24, ease: "easeInOut" }}
              className="overflow-hidden border-t border-slate-200 bg-slate-50 lg:hidden dark:border-slate-800 dark:bg-slate-950"
            >
              <div className="max-h-[70vh] space-y-1 overflow-y-auto px-4 py-4 sm:px-6">
                {NAV.map((item) => {
                  if (!item.columns) {
                    return (
                      <a
                        key={item.id}
                        href={item.href ?? "#"}
                        onClick={() => setMobileOpen(false)}
                        className="block rounded-xl px-3 py-3 text-sm font-semibold text-slate-800 outline-none transition-colors hover:bg-slate-200/70 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-100 dark:hover:bg-slate-800/70"
                      >
                        {item.label}
                      </a>
                    );
                  }
                  const sectionOpen = mobileSection === item.id;
                  const sectionId = `${uid}-m-${item.id}`;
                  return (
                    <div key={item.id} className="rounded-xl">
                      <button
                        type="button"
                        aria-expanded={sectionOpen}
                        aria-controls={sectionId}
                        onClick={() => setMobileSection((cur) => (cur === item.id ? null : item.id))}
                        className="flex w-full items-center justify-between rounded-xl px-3 py-3 text-sm font-semibold text-slate-800 outline-none transition-colors hover:bg-slate-200/70 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-100 dark:hover:bg-slate-800/70"
                      >
                        {item.label}
                        <IconChevron
                          className={`h-4 w-4 text-slate-400 transition-transform duration-200 ${sectionOpen ? "rotate-180" : ""}`}
                        />
                      </button>
                      <AnimatePresence initial={false}>
                        {sectionOpen && (
                          <motion.div
                            key="acc"
                            id={sectionId}
                            initial={reduce ? { opacity: 1 } : { opacity: 0, height: 0 }}
                            animate={reduce ? { opacity: 1 } : { opacity: 1, height: "auto" }}
                            exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}
                            transition={reduce ? { duration: 0 } : { duration: 0.2, ease: "easeInOut" }}
                            className="overflow-hidden"
                          >
                            <div className="space-y-0.5 px-1 pb-2 pt-1">
                              {item.columns.map((col) =>
                                col.links.map((link) => {
                                  const LinkIcon = link.icon;
                                  return (
                                    <a
                                      key={link.label}
                                      href={link.href}
                                      onClick={() => setMobileOpen(false)}
                                      className="flex items-start gap-3 rounded-lg p-2.5 outline-none transition-colors hover:bg-slate-200/60 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:hover:bg-slate-800/60"
                                    >
                                      <span className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 dark:bg-indigo-500/10 dark:text-indigo-300">
                                        <LinkIcon className="h-5 w-5" />
                                      </span>
                                      <span className="min-w-0">
                                        <span className="flex items-center gap-2 text-sm font-medium text-slate-900 dark:text-white">
                                          {link.label}
                                          {link.badge && (
                                            <span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-1.5 py-0.5 text-[0.6rem] font-semibold uppercase text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300">
                                              <span className="navx-dot h-1.5 w-1.5 rounded-full bg-emerald-500" />
                                              {link.badge}
                                            </span>
                                          )}
                                        </span>
                                        <span className="mt-0.5 block text-xs leading-snug text-slate-500 dark:text-slate-400">
                                          {link.desc}
                                        </span>
                                      </span>
                                    </a>
                                  );
                                }),
                              )}
                            </div>
                          </motion.div>
                        )}
                      </AnimatePresence>
                    </div>
                  );
                })}

                <div className="mt-3 flex flex-col gap-2 border-t border-slate-200 pt-4 dark:border-slate-800">
                  <a
                    href="#signin"
                    onClick={() => setMobileOpen(false)}
                    className="rounded-lg px-3 py-2.5 text-center text-sm font-semibold text-slate-800 ring-1 ring-inset ring-slate-300 outline-none transition-colors hover:bg-slate-200/70 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-100 dark:ring-slate-700 dark:hover:bg-slate-800/70"
                  >
                    Sign in
                  </a>
                  <a
                    href="#start"
                    onClick={() => setMobileOpen(false)}
                    className="inline-flex items-center justify-center gap-1.5 rounded-lg bg-indigo-600 px-3 py-2.5 text-sm font-semibold text-white outline-none transition-colors hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500"
                  >
                    Get started
                    <IconArrow className="h-4 w-4" />
                  </a>
                </div>
              </div>
            </motion.div>
          )}
        </AnimatePresence>
      </header>

      {/* ------------------------------ demo hero --------------------------- */}
      <div id="top" className="relative mx-auto max-w-7xl px-4 py-20 sm:px-6 sm:py-28 lg:px-8">
        <div className="mx-auto max-w-3xl text-center">
          <span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white/70 px-3 py-1 text-xs font-medium text-slate-600 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/70 dark:text-slate-300">
            <span className="navx-dot h-1.5 w-1.5 rounded-full bg-emerald-500" />
            Trusted by 4,000 product teams
          </span>
          <h1 className="mt-6 text-balance text-4xl font-semibold tracking-tight text-slate-900 sm:text-6xl dark:text-white">
            Ship product decisions,{" "}
            <span className="bg-gradient-to-r from-indigo-600 to-violet-500 bg-clip-text text-transparent dark:from-indigo-400 dark:to-violet-400">
              not spreadsheets
            </span>
          </h1>
          <p className="mx-auto mt-5 max-w-xl text-pretty text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-300">
            Sundial turns raw product events into decisions your whole team can act on. Hover the
            menu above to explore the platform, solutions, and developer tools.
          </p>
          <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
            <a
              href="#start"
              className="navx-cta inline-flex items-center gap-1.5 rounded-lg bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-sm outline-none transition-colors hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:focus-visible:ring-offset-slate-950"
            >
              <span className="navx-cta__sheen" aria-hidden="true" />
              Start free trial
              <IconArrow className="h-4 w-4" />
            </a>
            <a
              href="#demo"
              className="inline-flex items-center gap-1.5 rounded-lg px-5 py-3 text-sm font-semibold text-slate-800 ring-1 ring-inset ring-slate-300 outline-none transition-colors hover:bg-white focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-100 dark:ring-slate-700 dark:hover:bg-slate-900"
            >
              Book a demo
            </a>
          </div>
        </div>

        <dl className="mx-auto mt-16 grid max-w-3xl grid-cols-1 gap-px overflow-hidden rounded-2xl border border-slate-200 bg-slate-200 text-center shadow-sm sm:grid-cols-3 dark:border-slate-800 dark:bg-slate-800">
          {[
            { v: "1.4B", l: "events processed / day" },
            { v: "99.99%", l: "measured uptime" },
            { v: "SOC 2", l: "Type II certified" },
          ].map((s) => (
            <div key={s.l} className="bg-white px-6 py-8 dark:bg-slate-950">
              <dt className="text-3xl font-semibold tracking-tight text-slate-900 dark:text-white">{s.v}</dt>
              <dd className="mt-1 text-sm text-slate-500 dark:text-slate-400">{s.l}</dd>
            </div>
          ))}
        </dl>
      </div>
    </section>
  );
}

Dependencies

motion

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 quote

Similar components

Browse all →
Centred Logo Navbar

Centred Logo Navbar

Original

A three-column navbar with the brand logo centred between the primary links and the account actions, wrapped in a soft rounded card.

Left Logo Navbar With CTA

Left Logo Navbar With CTA

Original

A classic left-aligned logo navbar with centred navigation links and a prominent pill call-to-action button on the right.

Navbar With Dropdown Menu

Navbar With Dropdown Menu

Original

A navbar featuring a JS-free product dropdown built with native details and summary, revealing a two-item mega-menu panel with icons.

Glass Transparent Navbar

Glass Transparent Navbar

Original

A frosted glass navbar floating over a colourful gradient hero backdrop, using backdrop blur and translucent borders for a modern overlay header.

Responsive Navbar with Mobile Menu

Responsive Navbar with Mobile Menu

MIT

A marketing site header with a logo, inline nav links, login/register buttons, and a working hamburger toggle that reveals a stacked mobile menu below the md breakpoint. Fully keyboard/ARIA accessible with light and dark variants.

Underline Tabs

Underline Tabs

MIT

An accessible underline-style tab switcher with a bottom-border active indicator and a live content panel. State-driven with proper role=tablist/tab/tabpanel and aria-selected wiring, styled for light and dark modes.

Split Button Dropdown Menu

Split Button Dropdown Menu

MIT

A split-button control with a chevron trigger that opens a divided action menu, including a destructive delete item. Closes on outside-click and Escape, with a rotating chevron and full ARIA menu roles for both light and dark themes.

Glass Navbar

Glass Navbar

Original

glassmorphic sticky navbar

Mega Navbar

Mega Navbar

Original

navbar with a mega dropdown

Centered Logo Navbar

Centered Logo Navbar

Original

navbar with centred logo and split links

Sidebar Toggle Navbar

Sidebar Toggle Navbar

Original

navbar with a sidebar toggle

Search Navbar

Search Navbar

Original

navbar with an expanding search