Dropdown Navbar
Original · freenavbar with hover dropdowns
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"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
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 quoteSimilar components
Browse all →
Centred Logo Navbar
OriginalA 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
OriginalA classic left-aligned logo navbar with centred navigation links and a prominent pill call-to-action button on the right.

Navbar With Dropdown Menu
OriginalA 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
OriginalA 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
MITA 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
MITAn 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
MITA 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
Originalglassmorphic sticky navbar

Mega Navbar
Originalnavbar with a mega dropdown

Centered Logo Navbar
Originalnavbar with centred logo and split links

Sidebar Toggle Navbar
Originalnavbar with a sidebar toggle

Search Navbar
Originalnavbar with an expanding search

