Web InnoventixFreeCode

Centered Logo Navbar

Original · free

navbar with centred logo and split links

byWeb InnoventixReact + Tailwind
navxcenteredlogonavbars
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-centered-logo.json
navx-centered-logo.tsx
"use client";

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

type SubItem = {
  label: string;
  description: string;
  href: string;
  icon: () => ReactElement;
};

type NavLink = {
  label: string;
  href: string;
};

function IconRealtime(): ReactElement {
  return (
    <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
      <path
        d="M3 12h3l2.5-6 4 12 2.5-6H21"
        stroke="currentColor"
        strokeWidth="1.75"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function IconCohort(): ReactElement {
  return (
    <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
      <circle cx="8" cy="8" r="3.25" stroke="currentColor" strokeWidth="1.75" />
      <circle cx="16" cy="16" r="3.25" stroke="currentColor" strokeWidth="1.75" />
      <path d="M10.4 10.4 13.6 13.6" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
    </svg>
  );
}

function IconAlert(): ReactElement {
  return (
    <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
      <path
        d="M12 4c-2.5 0-4 2-4 4.5 0 3-1.5 4-2.5 5.5h13c-1-1.5-2.5-2.5-2.5-5.5C16 6 14.5 4 12 4Z"
        stroke="currentColor"
        strokeWidth="1.75"
        strokeLinejoin="round"
      />
      <path d="M10 18a2 2 0 0 0 4 0" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
    </svg>
  );
}

function IconSync(): ReactElement {
  return (
    <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
      <path
        d="M5 8a7 7 0 0 1 11.5-2.5L19 8M19 16a7 7 0 0 1-11.5 2.5L5 16"
        stroke="currentColor"
        strokeWidth="1.75"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path d="M19 4v4h-4M5 20v-4h4" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

const productMenu: SubItem[] = [
  {
    label: "Realtime dashboards",
    description: "Streaming metrics with sub-second refresh and shareable views.",
    href: "#realtime",
    icon: IconRealtime,
  },
  {
    label: "Cohort analysis",
    description: "Group users by behavior and track retention across releases.",
    href: "#cohorts",
    icon: IconCohort,
  },
  {
    label: "Alerts & anomalies",
    description: "Detect drift automatically and route to Slack or PagerDuty.",
    href: "#alerts",
    icon: IconAlert,
  },
  {
    label: "Warehouse sync",
    description: "Two-way sync with Snowflake, BigQuery, and Postgres.",
    href: "#warehouse",
    icon: IconSync,
  },
];

const leftLinks: NavLink[] = [
  { label: "Pricing", href: "#pricing" },
  { label: "Customers", href: "#customers" },
];

const rightLinks: NavLink[] = [
  { label: "Docs", href: "#docs" },
  { label: "Changelog", href: "#changelog" },
  { label: "Sign in", href: "#signin" },
];

export default function NavxCenteredLogo(): ReactElement {
  const reduce = useReducedMotion();
  const rawId = useId();
  const uid = rawId.replace(/[:]/g, "");
  const menuId = `${uid}-product`;
  const mobileId = `${uid}-mobile`;

  const [scrolled, setScrolled] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);
  const [mobileOpen, setMobileOpen] = useState(false);

  const sentinelRef = useRef<HTMLDivElement | null>(null);
  const menuWrapRef = useRef<HTMLDivElement | null>(null);
  const menuButtonRef = useRef<HTMLButtonElement | null>(null);
  const mobileButtonRef = useRef<HTMLButtonElement | null>(null);

  useEffect(() => {
    const node = sentinelRef.current;
    if (!node) return;
    const observer = new IntersectionObserver(
      (entries) => {
        const first = entries[0];
        if (first) setScrolled(!first.isIntersecting);
      },
      { rootMargin: "0px", threshold: 0 },
    );
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  useEffect(() => {
    if (!menuOpen) return;
    function onPointer(event: MouseEvent) {
      const wrap = menuWrapRef.current;
      if (wrap && event.target instanceof Node && !wrap.contains(event.target)) {
        setMenuOpen(false);
      }
    }
    document.addEventListener("mousedown", onPointer);
    return () => document.removeEventListener("mousedown", onPointer);
  }, [menuOpen]);

  useEffect(() => {
    function onKey(event: KeyboardEvent) {
      if (event.key !== "Escape") return;
      if (menuOpen) {
        setMenuOpen(false);
        menuButtonRef.current?.focus();
      }
      if (mobileOpen) {
        setMobileOpen(false);
        mobileButtonRef.current?.focus();
      }
    }
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [menuOpen, mobileOpen]);

  const linkClass =
    "rounded-md px-2 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-slate-300 dark:hover:text-white dark:focus-visible:ring-offset-slate-950";

  return (
    <section className="relative w-full bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes navx_live_pulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.35; transform: scale(0.72); }
        }
        @keyframes navx_mark_drift {
          0%, 100% { transform: translateY(0); }
          50% { transform: translateY(-1.5px); }
        }
        .navx-live-dot { animation: navx_live_pulse 2s ease-in-out infinite; }
        .navx-mark { animation: navx_mark_drift 5s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .navx-live-dot, .navx-mark { animation: none !important; }
        }
      `}</style>

      <div ref={sentinelRef} aria-hidden="true" className="absolute inset-x-0 top-0 h-px" />

      <header
        className={`sticky top-0 z-40 w-full border-b transition-colors duration-300 ${
          scrolled || menuOpen
            ? "border-slate-200/80 bg-white/85 backdrop-blur-xl dark:border-slate-800/80 dark:bg-slate-950/80"
            : "border-transparent bg-transparent"
        }`}
      >
        <nav aria-label="Primary" className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
          <div className="grid h-16 grid-cols-[1fr_auto_1fr] items-center gap-4 lg:h-20">
            {/* Left region: hamburger (mobile) + split links (desktop) */}
            <div className="flex items-center justify-start gap-1">
              <button
                ref={mobileButtonRef}
                type="button"
                onClick={() => setMobileOpen((v) => !v)}
                aria-expanded={mobileOpen}
                aria-controls={mobileId}
                aria-label={mobileOpen ? "Close menu" : "Open menu"}
                className="inline-flex h-10 w-10 items-center justify-center rounded-lg text-slate-700 transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white lg:hidden dark:text-slate-200 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-950"
              >
                {mobileOpen ? (
                  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
                    <path d="m6 6 12 12M18 6 6 18" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" />
                  </svg>
                ) : (
                  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
                    <path d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" />
                  </svg>
                )}
              </button>

              <div className="hidden items-center gap-1 lg:flex" ref={menuWrapRef}>
                <div className="relative">
                  <button
                    ref={menuButtonRef}
                    type="button"
                    onClick={() => setMenuOpen((v) => !v)}
                    aria-expanded={menuOpen}
                    aria-controls={menuId}
                    aria-haspopup="true"
                    className="inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:text-slate-950 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-slate-300 dark:hover:text-white dark:focus-visible:ring-offset-slate-950"
                  >
                    Platform
                    <motion.span
                      aria-hidden="true"
                      animate={{ rotate: menuOpen ? 180 : 0 }}
                      transition={reduce ? { duration: 0 } : { type: "spring", stiffness: 400, damping: 30 }}
                      className="inline-flex"
                    >
                      <svg viewBox="0 0 24 24" fill="none" className="h-4 w-4">
                        <path d="m6 9 6 6 6-6" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
                      </svg>
                    </motion.span>
                  </button>

                  <AnimatePresence>
                    {menuOpen ? (
                      <motion.div
                        key="product-menu"
                        id={menuId}
                        role="region"
                        aria-label="Platform"
                        initial={reduce ? { opacity: 0 } : { opacity: 0, y: 8, scale: 0.98 }}
                        animate={reduce ? { opacity: 1 } : { opacity: 1, y: 0, scale: 1 }}
                        exit={reduce ? { opacity: 0 } : { opacity: 0, y: 6, scale: 0.98 }}
                        transition={reduce ? { duration: 0 } : { duration: 0.18, ease: [0.22, 1, 0.36, 1] }}
                        className="absolute left-0 top-full z-50 mt-3 w-[26rem] origin-top-left rounded-2xl border border-slate-200 bg-white p-2 shadow-2xl shadow-slate-900/10 dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/40"
                      >
                        <ul className="grid gap-1">
                          {productMenu.map((item) => {
                            const Icon = item.icon;
                            return (
                              <li key={item.label}>
                                <a
                                  href={item.href}
                                  onClick={() => setMenuOpen(false)}
                                  className="group flex items-start gap-3 rounded-xl p-3 transition-colors hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:hover:bg-slate-800/70 dark:focus-visible:ring-offset-slate-900"
                                >
                                  <span className="mt-0.5 inline-flex h-10 w-10 flex-none 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-500/20">
                                    <Icon />
                                  </span>
                                  <span className="min-w-0">
                                    <span className="block text-sm font-semibold text-slate-900 dark:text-white">
                                      {item.label}
                                    </span>
                                    <span className="mt-0.5 block text-xs leading-relaxed text-slate-500 dark:text-slate-400">
                                      {item.description}
                                    </span>
                                  </span>
                                </a>
                              </li>
                            );
                          })}
                        </ul>
                        <div className="mt-1 flex items-center justify-between rounded-xl bg-slate-50 px-3 py-2.5 dark:bg-slate-800/60">
                          <span className="text-xs font-medium text-slate-500 dark:text-slate-400">
                            New: anomaly forecasting
                          </span>
                          <a
                            href="#tour"
                            onClick={() => setMenuOpen(false)}
                            className="inline-flex items-center gap-1 rounded-md text-xs font-semibold text-indigo-600 hover:text-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:text-indigo-300 dark:hover:text-indigo-200 dark:focus-visible:ring-offset-slate-800"
                          >
                            Take the tour
                            <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-3.5 w-3.5">
                              <path d="M5 12h14m-6-6 6 6-6 6" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
                            </svg>
                          </a>
                        </div>
                      </motion.div>
                    ) : null}
                  </AnimatePresence>
                </div>

                {leftLinks.map((link) => (
                  <a key={link.label} href={link.href} className={linkClass}>
                    {link.label}
                  </a>
                ))}
              </div>
            </div>

            {/* Center: logo */}
            <a
              href="#top"
              aria-label="Meridian, home"
              className="group flex items-center justify-center gap-2.5 rounded-lg px-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
            >
              <span className="navx-mark inline-flex">
                <svg viewBox="0 0 32 32" fill="none" aria-hidden="true" className="h-8 w-8">
                  <circle cx="16" cy="16" r="13" className="stroke-slate-900 dark:stroke-white" strokeWidth="1.75" />
                  <path
                    d="M16 3c-4 3.4-6 7.9-6 13s2 9.6 6 13c4-3.4 6-7.9 6-13S20 6.4 16 3Z"
                    className="stroke-indigo-500"
                    strokeWidth="1.75"
                  />
                  <path d="M3.5 16h25" className="stroke-slate-900/50 dark:stroke-white/50" strokeWidth="1.5" />
                </svg>
              </span>
              <span className="text-lg font-semibold tracking-tight text-slate-900 dark:text-white">
                Meridian
              </span>
            </a>

            {/* Right region: split links (desktop) + CTA */}
            <div className="flex items-center justify-end gap-1.5">
              <div className="hidden items-center gap-1 lg:flex">
                {rightLinks.map((link) => (
                  <a key={link.label} href={link.href} className={`relative ${linkClass}`}>
                    {link.label}
                    {link.label === "Changelog" ? (
                      <span
                        aria-hidden="true"
                        className="navx-live-dot absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-emerald-500"
                      />
                    ) : null}
                  </a>
                ))}
              </div>
              <a
                href="#start"
                className="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-3.5 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
              >
                Start free
              </a>
            </div>
          </div>
        </nav>

        {/* Mobile panel */}
        <AnimatePresence>
          {mobileOpen ? (
            <motion.div
              key="mobile-panel"
              id={mobileId}
              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.25, ease: [0.22, 1, 0.36, 1] }}
              className="overflow-hidden border-t border-slate-200 bg-white lg:hidden dark:border-slate-800 dark:bg-slate-950"
            >
              <div className="mx-auto max-w-7xl space-y-6 px-4 py-5 sm:px-6">
                <div>
                  <p className="px-1 pb-2 text-xs font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-500">
                    Platform
                  </p>
                  <ul className="grid gap-1">
                    {productMenu.map((item) => {
                      const Icon = item.icon;
                      return (
                        <li key={item.label}>
                          <a
                            href={item.href}
                            onClick={() => setMobileOpen(false)}
                            className="flex items-center gap-3 rounded-xl px-2 py-2.5 transition-colors hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:hover:bg-slate-900 dark:focus-visible:ring-offset-slate-950"
                          >
                            <span className="inline-flex h-9 w-9 flex-none items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 dark:bg-indigo-500/10 dark:text-indigo-300">
                              <Icon />
                            </span>
                            <span className="text-sm font-medium text-slate-800 dark:text-slate-100">
                              {item.label}
                            </span>
                          </a>
                        </li>
                      );
                    })}
                  </ul>
                </div>

                <div className="grid gap-1 border-t border-slate-100 pt-4 dark:border-slate-900">
                  {[...leftLinks, ...rightLinks].map((link) => (
                    <a
                      key={link.label}
                      href={link.href}
                      onClick={() => setMobileOpen(false)}
                      className="flex items-center justify-between rounded-lg px-2 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-slate-200 dark:hover:bg-slate-900 dark:focus-visible:ring-offset-slate-950"
                    >
                      {link.label}
                      <svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-4 w-4 text-slate-400">
                        <path d="m9 6 6 6-6 6" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
                      </svg>
                    </a>
                  ))}
                </div>

                <a
                  href="#start"
                  onClick={() => setMobileOpen(false)}
                  className="flex w-full items-center justify-center rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
                >
                  Start free
                </a>
              </div>
            </motion.div>
          ) : null}
        </AnimatePresence>
      </header>

      {/* Demo content so the sticky + scroll-aware backdrop is observable */}
      <div className="mx-auto max-w-7xl px-4 pb-24 pt-16 sm:px-6 lg:px-8 lg:pt-24">
        <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 px-3 py-1 text-xs font-medium text-slate-600 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-300">
            <span className="navx-live-dot h-1.5 w-1.5 rounded-full bg-emerald-500" aria-hidden="true" />
            Now streaming events in 14ms p95
          </span>
          <h1 className="mt-6 text-balance text-4xl font-semibold tracking-tight text-slate-900 sm:text-5xl dark:text-white">
            Product analytics that keeps up with your release cadence
          </h1>
          <p className="mx-auto mt-5 max-w-xl text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-400">
            Meridian unifies realtime dashboards, cohort retention, and anomaly alerts
            over your own warehouse. Scroll to watch the navigation settle into place.
          </p>
          <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
            <a
              href="#start"
              className="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-indigo-500 focus-visible: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"
            >
              Start free
            </a>
            <a
              href="#docs"
              className="inline-flex items-center justify-center rounded-lg border border-slate-300 bg-white px-5 py-2.5 text-sm font-semibold text-slate-800 transition-colors hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-950"
            >
              Read the docs
            </a>
          </div>
        </div>

        <div className="mt-20 grid gap-4 sm:grid-cols-3">
          {productMenu.slice(0, 3).map((item) => {
            const Icon = item.icon;
            return (
              <div
                key={item.label}
                className="rounded-2xl border border-slate-200 bg-white p-6 dark:border-slate-800 dark:bg-slate-900"
              >
                <span className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-50 text-indigo-600 dark:bg-indigo-500/10 dark:text-indigo-300">
                  <Icon />
                </span>
                <h2 className="mt-4 text-sm font-semibold text-slate-900 dark:text-white">{item.label}</h2>
                <p className="mt-1.5 text-sm leading-relaxed text-slate-500 dark:text-slate-400">
                  {item.description}
                </p>
              </div>
            );
          })}
        </div>

        <div className="h-[60vh]" aria-hidden="true" />
      </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

Sidebar Toggle Navbar

Sidebar Toggle Navbar

Original

navbar with a sidebar toggle

Search Navbar

Search Navbar

Original

navbar with an expanding search

Gradient Navbar

Gradient Navbar

Original

gradient navbar