Web InnoventixFreeCode

App Navbar

Original · free

app top bar with avatar and actions

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

import {
  useCallback,
  useEffect,
  useId,
  useRef,
  useState,
  type KeyboardEvent as ReactKeyboardEvent,
  type ReactNode,
  type RefObject,
} from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

/* ---------------------------------- data ---------------------------------- */

type NavItem = { id: string; label: string };

type NotifKind = "approval" | "comment" | "deploy" | "digest";

type Notif = {
  id: string;
  title: string;
  meta: string;
  time: string;
  read: boolean;
  kind: NotifKind;
};

type MenuAction = {
  id: string;
  label: string;
  hint?: string;
  icon: ReactNode;
  danger?: boolean;
};

const NAV_ITEMS: NavItem[] = [
  { id: "overview", label: "Overview" },
  { id: "pipeline", label: "Pipeline" },
  { id: "reports", label: "Reports" },
  { id: "insights", label: "Insights" },
];

const INITIAL_NOTIFS: Notif[] = [
  {
    id: "n1",
    title: "Priya approved the Q3 revenue forecast",
    meta: "Finance workspace",
    time: "4m",
    read: false,
    kind: "approval",
  },
  {
    id: "n2",
    title: "New comment on “Enterprise pipeline health”",
    meta: "Marcus Lindqvist",
    time: "22m",
    read: false,
    kind: "comment",
  },
  {
    id: "n3",
    title: "Deploy #482 finished in 3m 14s",
    meta: "production · main",
    time: "1h",
    read: false,
    kind: "deploy",
  },
  {
    id: "n4",
    title: "Your weekly digest is ready to review",
    meta: "7 metrics moved",
    time: "3h",
    read: true,
    kind: "digest",
  },
];

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

type IconProps = { className?: string };

function SearchIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <circle cx="11" cy="11" r="7" />
      <path d="m20 20-3.2-3.2" />
    </svg>
  );
}

function BellIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6" />
      <path d="M9.5 20a2.5 2.5 0 0 0 5 0" />
    </svg>
  );
}

function ChevronDownIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.9} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="m6 9 6 6 6-6" />
    </svg>
  );
}

function PlusIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="M12 5v14M5 12h14" />
    </svg>
  );
}

function MenuIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.9} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="M4 6h16M4 12h16M4 18h16" />
    </svg>
  );
}

function CloseIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.9} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="M6 6l12 12M18 6L6 18" />
    </svg>
  );
}

function UserIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <circle cx="12" cy="8" r="4" />
      <path d="M4 20a8 8 0 0 1 16 0" />
    </svg>
  );
}

function SettingsIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <circle cx="12" cy="12" r="3" />
      <path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 1 1-4 0v-.1a1.6 1.6 0 0 0-2.7-1.1l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 4.6 15H4.5a2 2 0 1 1 0-4h.1a1.6 1.6 0 0 0 1.1-2.7l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1A1.6 1.6 0 0 0 11 4.6V4.5a2 2 0 1 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0 1.1 2.7h.1a2 2 0 1 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1Z" />
    </svg>
  );
}

function BillingIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <rect x="2.5" y="5" width="19" height="14" rx="2.5" />
      <path d="M2.5 10h19" />
      <path d="M6.5 15h3" />
    </svg>
  );
}

function CommandIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="M9 6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3Z" />
    </svg>
  );
}

function LogoutIcon({ className }: IconProps) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <path d="M15 4h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-3" />
      <path d="M10 17l-5-5 5-5" />
      <path d="M5 12h11" />
    </svg>
  );
}

function NotifIcon({ kind, className }: { kind: NotifKind; className?: string }) {
  if (kind === "approval") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
        <path d="m5 13 4 4L19 7" />
      </svg>
    );
  }
  if (kind === "comment") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
        <path d="M21 12a8 8 0 0 1-11.5 7.2L4 20l.8-5.5A8 8 0 1 1 21 12Z" />
      </svg>
    );
  }
  if (kind === "deploy") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
        <path d="M13 2 4 14h6l-1 8 9-12h-6l1-8Z" />
      </svg>
    );
  }
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">
      <rect x="3" y="5" width="18" height="14" rx="2.5" />
      <path d="m4 7 8 6 8-6" />
    </svg>
  );
}

const KIND_TONE: Record<NotifKind, string> = {
  approval:
    "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300",
  comment:
    "bg-sky-100 text-sky-700 dark:bg-sky-500/15 dark:text-sky-300",
  deploy:
    "bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300",
  digest:
    "bg-amber-100 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300",
};

/* -------------------------------- helpers --------------------------------- */

function useDismiss<T extends HTMLElement>(open: boolean, ref: RefObject<T | null>, onClose: () => void) {
  useEffect(() => {
    if (!open) return;
    const onDown = (e: PointerEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) onClose();
    };
    document.addEventListener("pointerdown", onDown);
    return () => document.removeEventListener("pointerdown", onDown);
  }, [open, ref, onClose]);
}

const RING =
  "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-indigo-400 dark:focus-visible:ring-offset-slate-950";

/* ---------------------------- notifications ------------------------------- */

function NotificationsButton({
  notifs,
  onMarkAll,
  onMarkOne,
  reduce,
}: {
  notifs: Notif[];
  onMarkAll: () => void;
  onMarkOne: (id: string) => void;
  reduce: boolean;
}) {
  const [open, setOpen] = useState(false);
  const wrapRef = useRef<HTMLDivElement>(null);
  const btnRef = useRef<HTMLButtonElement>(null);
  const panelRef = useRef<HTMLDivElement>(null);
  const panelId = useId();
  const unread = notifs.filter((n) => !n.read).length;

  const close = useCallback(() => setOpen(false), []);
  useDismiss(open, wrapRef, close);

  useEffect(() => {
    if (open) {
      const t = window.setTimeout(() => {
        panelRef.current?.querySelector<HTMLElement>("[data-autofocus]")?.focus();
      }, 0);
      return () => window.clearTimeout(t);
    }
  }, [open]);

  const onKeyDown = (e: ReactKeyboardEvent) => {
    if (e.key === "Escape") {
      e.stopPropagation();
      setOpen(false);
      btnRef.current?.focus();
    }
  };

  return (
    <div ref={wrapRef} className="relative" onKeyDown={onKeyDown}>
      <button
        ref={btnRef}
        type="button"
        aria-haspopup="dialog"
        aria-expanded={open}
        aria-controls={open ? panelId : undefined}
        aria-label={`Notifications${unread ? `, ${unread} unread` : ""}`}
        onClick={() => setOpen((v) => !v)}
        className={`relative grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white ${RING}`}
      >
        <BellIcon className="h-[18px] w-[18px]" />
        {unread > 0 && (
          <span className="absolute right-1.5 top-1.5 flex h-4 min-w-4 items-center justify-center">
            <span className="navxapp-ping absolute inline-flex h-full w-full rounded-full bg-rose-400/70" />
            <span className="relative inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-rose-500 px-1 text-[10px] font-semibold leading-none text-white ring-2 ring-white dark:ring-slate-900">
              {unread}
            </span>
          </span>
        )}
      </button>

      <AnimatePresence>
        {open && (
          <motion.div
            ref={panelRef}
            id={panelId}
            role="dialog"
            aria-label="Notifications"
            initial={reduce ? false : { opacity: 0, y: -8, scale: 0.98 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={reduce ? { opacity: 0 } : { opacity: 0, y: -8, scale: 0.98 }}
            transition={{ duration: reduce ? 0 : 0.16, ease: "easeOut" }}
            style={{ transformOrigin: "top right" }}
            className="absolute right-0 z-50 mt-2 w-[calc(100vw-2rem)] max-w-sm overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-2xl shadow-slate-900/10 dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/40"
          >
            <div className="flex items-center justify-between gap-3 border-b border-slate-100 px-4 py-3 dark:border-slate-800">
              <div>
                <p className="text-sm font-semibold text-slate-900 dark:text-white">Notifications</p>
                <p className="text-xs text-slate-500 dark:text-slate-400">
                  {unread > 0 ? `${unread} unread` : "You’re all caught up"}
                </p>
              </div>
              <button
                type="button"
                data-autofocus
                onClick={onMarkAll}
                disabled={unread === 0}
                className={`rounded-lg px-2.5 py-1.5 text-xs font-medium text-indigo-600 transition-colors hover:bg-indigo-50 disabled:cursor-not-allowed disabled:opacity-40 dark:text-indigo-300 dark:hover:bg-indigo-500/10 ${RING}`}
              >
                Mark all read
              </button>
            </div>

            <ul className="max-h-[19rem] divide-y divide-slate-100 overflow-y-auto dark:divide-slate-800">
              {notifs.map((n) => (
                <li key={n.id}>
                  <button
                    type="button"
                    onClick={() => onMarkOne(n.id)}
                    className={`flex w-full items-start gap-3 px-4 py-3 text-left transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/60 ${RING}`}
                  >
                    <span className={`mt-0.5 grid h-9 w-9 shrink-0 place-items-center rounded-full ${KIND_TONE[n.kind]}`}>
                      <NotifIcon kind={n.kind} className="h-[18px] w-[18px]" />
                    </span>
                    <span className="min-w-0 flex-1">
                      <span className="flex items-baseline justify-between gap-2">
                        <span className={`truncate text-sm ${n.read ? "font-normal text-slate-600 dark:text-slate-400" : "font-semibold text-slate-900 dark:text-white"}`}>
                          {n.title}
                        </span>
                        <span className="shrink-0 text-[11px] tabular-nums text-slate-400 dark:text-slate-500">{n.time}</span>
                      </span>
                      <span className="mt-0.5 block truncate text-xs text-slate-500 dark:text-slate-400">{n.meta}</span>
                    </span>
                    {!n.read && (
                      <span className="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-indigo-500" aria-hidden="true" />
                    )}
                  </button>
                </li>
              ))}
            </ul>

            <div className="border-t border-slate-100 p-2 dark:border-slate-800">
              <button
                type="button"
                className={`w-full rounded-xl px-3 py-2 text-center text-sm font-medium text-slate-700 transition-colors hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800 ${RING}`}
              >
                View all activity
              </button>
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

/* ------------------------------ avatar menu ------------------------------- */

function AvatarMenu({ reduce }: { reduce: boolean }) {
  const actions: MenuAction[] = [
    { id: "profile", label: "Your profile", icon: <UserIcon className="h-[18px] w-[18px]" /> },
    { id: "team", label: "Team settings", icon: <SettingsIcon className="h-[18px] w-[18px]" /> },
    { id: "billing", label: "Billing & usage", hint: "Pro", icon: <BillingIcon className="h-[18px] w-[18px]" /> },
    { id: "command", label: "Command menu", hint: "⌘K", icon: <CommandIcon className="h-[18px] w-[18px]" /> },
  ];

  const [open, setOpen] = useState(false);
  const [active, setActive] = useState(0);
  const wrapRef = useRef<HTMLDivElement>(null);
  const btnRef = useRef<HTMLButtonElement>(null);
  const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);
  const menuId = useId();
  const total = actions.length + 1; // + sign out

  const close = useCallback(() => setOpen(false), []);
  useDismiss(open, wrapRef, close);

  useEffect(() => {
    if (open) itemRefs.current[active]?.focus();
  }, [open, active]);

  const openWith = (index: number) => {
    setActive(index);
    setOpen(true);
  };

  const onTriggerKey = (e: ReactKeyboardEvent) => {
    if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      openWith(0);
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      openWith(total - 1);
    }
  };

  const onMenuKey = (e: ReactKeyboardEvent) => {
    if (e.key === "ArrowDown") {
      e.preventDefault();
      setActive((i) => (i + 1) % total);
    } else if (e.key === "ArrowUp") {
      e.preventDefault();
      setActive((i) => (i - 1 + total) % total);
    } else if (e.key === "Home") {
      e.preventDefault();
      setActive(0);
    } else if (e.key === "End") {
      e.preventDefault();
      setActive(total - 1);
    } else if (e.key === "Escape") {
      e.preventDefault();
      setOpen(false);
      btnRef.current?.focus();
    } else if (e.key === "Tab") {
      setOpen(false);
    }
  };

  const rowBase =
    "flex w-full items-center gap-3 rounded-xl px-2.5 py-2 text-sm outline-none transition-colors";

  return (
    <div ref={wrapRef} className="relative">
      <button
        ref={btnRef}
        type="button"
        id={`${menuId}-trigger`}
        aria-haspopup="menu"
        aria-expanded={open}
        aria-controls={open ? menuId : undefined}
        onClick={() => (open ? setOpen(false) : openWith(0))}
        onKeyDown={onTriggerKey}
        className={`flex items-center gap-2 rounded-full border border-slate-200 bg-white py-1 pl-1 pr-1.5 transition-colors hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:hover:bg-slate-800 sm:pr-2.5 ${RING}`}
      >
        <span className="relative grid h-8 w-8 shrink-0 place-items-center rounded-full bg-gradient-to-br from-indigo-500 via-violet-500 to-sky-500 text-xs font-semibold text-white">
          SR
          <span className="absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full border-2 border-white bg-emerald-500 dark:border-slate-900" aria-hidden="true" />
        </span>
        <span className="hidden text-left leading-tight sm:block">
          <span className="block text-sm font-semibold text-slate-900 dark:text-white">Salman Riaz</span>
          <span className="block text-[11px] text-slate-500 dark:text-slate-400">Admin</span>
        </span>
        <ChevronDownIcon className={`hidden h-4 w-4 text-slate-400 transition-transform duration-200 sm:block ${open ? "rotate-180" : ""}`} />
      </button>

      <AnimatePresence>
        {open && (
          <motion.div
            id={menuId}
            role="menu"
            aria-labelledby={`${menuId}-trigger`}
            onKeyDown={onMenuKey}
            initial={reduce ? false : { opacity: 0, y: -8, scale: 0.98 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={reduce ? { opacity: 0 } : { opacity: 0, y: -8, scale: 0.98 }}
            transition={{ duration: reduce ? 0 : 0.16, ease: "easeOut" }}
            style={{ transformOrigin: "top right" }}
            className="absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-2xl border border-slate-200 bg-white p-1.5 shadow-2xl shadow-slate-900/10 dark:border-slate-800 dark:bg-slate-900 dark:shadow-black/40"
          >
            <div className="flex items-center gap-3 rounded-xl px-2.5 py-2.5">
              <span className="grid h-10 w-10 shrink-0 place-items-center rounded-full bg-gradient-to-br from-indigo-500 via-violet-500 to-sky-500 text-sm font-semibold text-white">
                SR
              </span>
              <span className="min-w-0">
                <span className="block truncate text-sm font-semibold text-slate-900 dark:text-white">Salman Riaz</span>
                <span className="block truncate text-xs text-slate-500 dark:text-slate-400">salman@cadence.io</span>
              </span>
            </div>

            <div className="my-1 h-px bg-slate-100 dark:bg-slate-800" />

            {actions.map((a, i) => (
              <button
                key={a.id}
                ref={(el) => {
                  itemRefs.current[i] = el;
                }}
                type="button"
                role="menuitem"
                tabIndex={active === i ? 0 : -1}
                onFocus={() => setActive(i)}
                onClick={() => {
                  setOpen(false);
                  btnRef.current?.focus();
                }}
                className={`${rowBase} text-slate-700 hover:bg-slate-100 focus-visible:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800 dark:focus-visible:bg-slate-800`}
              >
                <span className="text-slate-400 dark:text-slate-500">{a.icon}</span>
                <span className="flex-1 text-left">{a.label}</span>
                {a.hint && (
                  <span className="rounded-md border border-slate-200 px-1.5 py-0.5 text-[10px] font-medium text-slate-500 dark:border-slate-700 dark:text-slate-400">
                    {a.hint}
                  </span>
                )}
              </button>
            ))}

            <div className="my-1 h-px bg-slate-100 dark:bg-slate-800" />

            <button
              ref={(el) => {
                itemRefs.current[actions.length] = el;
              }}
              type="button"
              role="menuitem"
              tabIndex={active === actions.length ? 0 : -1}
              onFocus={() => setActive(actions.length)}
              onClick={() => {
                setOpen(false);
                btnRef.current?.focus();
              }}
              className={`${rowBase} text-rose-600 hover:bg-rose-50 focus-visible:bg-rose-50 dark:text-rose-400 dark:hover:bg-rose-500/10 dark:focus-visible:bg-rose-500/10`}
            >
              <LogoutIcon className="h-[18px] w-[18px]" />
              <span className="flex-1 text-left">Sign out</span>
            </button>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

/* -------------------------------- main bar -------------------------------- */

export default function NavxApp() {
  const prefersReduced = useReducedMotion();
  const reduce = prefersReduced ?? false;

  const [activeNav, setActiveNav] = useState<string>("overview");
  const [notifs, setNotifs] = useState<Notif[]>(INITIAL_NOTIFS);
  const [query, setQuery] = useState("");
  const [mobileOpen, setMobileOpen] = useState(false);
  const searchRef = useRef<HTMLInputElement>(null);

  const markAll = useCallback(
    () => setNotifs((prev) => prev.map((n) => ({ ...n, read: true }))),
    [],
  );
  const markOne = useCallback(
    (id: string) => setNotifs((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n))),
    [],
  );

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        searchRef.current?.focus();
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const activeLabel = NAV_ITEMS.find((n) => n.id === activeNav)?.label ?? "Overview";

  return (
    <section className="relative w-full bg-slate-50 px-4 py-10 text-slate-900 dark:bg-slate-950 dark:text-white sm:px-6 sm:py-14">
      <style>{`
        @keyframes navxapp-ping {
          0% { transform: scale(1); opacity: 0.7; }
          70% { transform: scale(2.1); opacity: 0; }
          100% { transform: scale(2.1); opacity: 0; }
        }
        .navxapp-ping { animation: navxapp-ping 2.4s cubic-bezier(0, 0, 0.2, 1) infinite; }
        @keyframes navxapp-rise {
          from { opacity: 0; transform: translateY(10px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .navxapp-rise { animation: navxapp-rise 0.5s ease-out both; }
        @media (prefers-reduced-motion: reduce) {
          .navxapp-ping, .navxapp-rise { animation: none !important; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        {/* ---------------------------- the top bar ---------------------------- */}
        <header className="relative z-20 rounded-2xl border border-slate-200 bg-white/85 shadow-sm shadow-slate-900/5 backdrop-blur-md dark:border-slate-800 dark:bg-slate-900/80 dark:shadow-black/20">
          <div className="flex items-center gap-2 px-3 py-2.5 sm:gap-4 sm:px-4">
            {/* mobile menu toggle */}
            <button
              type="button"
              aria-label="Toggle navigation"
              aria-expanded={mobileOpen}
              onClick={() => setMobileOpen((v) => !v)}
              className={`grid h-10 w-10 shrink-0 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-100 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 md:hidden ${RING}`}
            >
              {mobileOpen ? <CloseIcon className="h-5 w-5" /> : <MenuIcon className="h-5 w-5" />}
            </button>

            {/* brand */}
            <a
              href="#"
              className={`flex shrink-0 items-center gap-2.5 rounded-xl px-1 py-1 ${RING}`}
            >
              <span className="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-indigo-500 via-violet-500 to-sky-500 shadow-lg shadow-indigo-500/30">
                <svg viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth={2.2} strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5" aria-hidden="true">
                  <path d="M3 14c2.5 0 2.5-6 5-6s2.5 8 5 8 2.5-6 5-6" />
                </svg>
              </span>
              <span className="hidden text-[17px] font-semibold tracking-tight text-slate-900 dark:text-white sm:block">
                Cadence
              </span>
            </a>

            {/* desktop nav */}
            <nav aria-label="Primary" className="ml-2 hidden items-center gap-1 md:flex">
              {NAV_ITEMS.map((item) => {
                const isActive = item.id === activeNav;
                return (
                  <button
                    key={item.id}
                    type="button"
                    aria-current={isActive ? "page" : undefined}
                    onClick={() => setActiveNav(item.id)}
                    className={`relative rounded-lg px-3 py-2 text-sm font-medium transition-colors ${RING} ${
                      isActive
                        ? "text-slate-900 dark:text-white"
                        : "text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white"
                    }`}
                  >
                    {item.label}
                    {isActive && (
                      <motion.span
                        layoutId={reduce ? undefined : "navxapp-underline"}
                        className="absolute inset-x-2 -bottom-0.5 h-0.5 rounded-full bg-indigo-500 dark:bg-indigo-400"
                      />
                    )}
                  </button>
                );
              })}
            </nav>

            <div className="flex-1" />

            {/* search (desktop) */}
            <div className="relative hidden lg:block">
              <SearchIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-slate-400" />
              <input
                ref={searchRef}
                type="search"
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === "Escape") e.currentTarget.blur();
                }}
                placeholder="Search Cadence"
                aria-label="Search Cadence"
                className={`h-10 w-52 rounded-xl border border-slate-200 bg-slate-50 pl-10 pr-14 text-sm text-slate-900 placeholder:text-slate-400 transition-[width,background,border] focus:w-72 focus:border-indigo-400 focus:bg-white dark:border-slate-800 dark:bg-slate-950/60 dark:text-white dark:placeholder:text-slate-500 dark:focus:border-indigo-500 dark:focus:bg-slate-950 ${RING}`}
              />
              <kbd className="pointer-events-none absolute right-2.5 top-1/2 hidden -translate-y-1/2 items-center gap-0.5 rounded-md border border-slate-200 bg-white px-1.5 py-0.5 text-[10px] font-medium text-slate-500 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-400 xl:flex">
                ⌘K
              </kbd>
            </div>

            {/* new report CTA */}
            <button
              type="button"
              className={`hidden items-center gap-1.5 rounded-xl bg-slate-900 px-3.5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200 sm:inline-flex ${RING}`}
            >
              <PlusIcon className="h-4 w-4" />
              New report
            </button>

            <NotificationsButton notifs={notifs} onMarkAll={markAll} onMarkOne={markOne} reduce={reduce} />

            <AvatarMenu reduce={reduce} />
          </div>

          {/* mobile expandable panel */}
          <AnimatePresence initial={false}>
            {mobileOpen && (
              <motion.div
                initial={reduce ? false : { height: 0, opacity: 0 }}
                animate={{ height: "auto", opacity: 1 }}
                exit={reduce ? { opacity: 0 } : { height: 0, opacity: 0 }}
                transition={{ duration: reduce ? 0 : 0.22, ease: "easeOut" }}
                className="overflow-hidden border-t border-slate-100 md:hidden dark:border-slate-800"
              >
                <div className="space-y-3 p-3">
                  <div className="relative">
                    <SearchIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-slate-400" />
                    <input
                      type="search"
                      value={query}
                      onChange={(e) => setQuery(e.target.value)}
                      placeholder="Search Cadence"
                      aria-label="Search Cadence"
                      className={`h-11 w-full rounded-xl border border-slate-200 bg-slate-50 pl-10 pr-3 text-sm text-slate-900 placeholder:text-slate-400 dark:border-slate-800 dark:bg-slate-950/60 dark:text-white dark:placeholder:text-slate-500 ${RING}`}
                    />
                  </div>
                  <nav aria-label="Mobile" className="grid gap-1">
                    {NAV_ITEMS.map((item) => {
                      const isActive = item.id === activeNav;
                      return (
                        <button
                          key={item.id}
                          type="button"
                          aria-current={isActive ? "page" : undefined}
                          onClick={() => {
                            setActiveNav(item.id);
                            setMobileOpen(false);
                          }}
                          className={`flex items-center justify-between rounded-xl px-3 py-2.5 text-sm font-medium transition-colors ${RING} ${
                            isActive
                              ? "bg-indigo-50 text-indigo-700 dark:bg-indigo-500/10 dark:text-indigo-300"
                              : "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"
                          }`}
                        >
                          {item.label}
                          {isActive && <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" aria-hidden="true" />}
                        </button>
                      );
                    })}
                  </nav>
                  <button
                    type="button"
                    className={`flex w-full items-center justify-center gap-1.5 rounded-xl bg-slate-900 px-3.5 py-3 text-sm font-semibold text-white dark:bg-white dark:text-slate-900 ${RING}`}
                  >
                    <PlusIcon className="h-4 w-4" />
                    New report
                  </button>
                </div>
              </motion.div>
            )}
          </AnimatePresence>
        </header>

        {/* ------------------------ page context beneath bar ------------------- */}
        <div className="navxapp-rise mt-8 px-1">
          <p className="text-sm font-medium text-indigo-600 dark:text-indigo-400">{activeLabel}</p>
          <h1 className="mt-1 text-2xl font-semibold tracking-tight text-slate-900 dark:text-white sm:text-3xl">
            Good afternoon, Salman
          </h1>
          <p className="mt-2 max-w-xl text-sm text-slate-500 dark:text-slate-400">
            Pipeline velocity is up 12% week over week. Three approvals are waiting on you and the
            Q3 forecast just cleared review.
          </p>

          <div className="mt-6 grid grid-cols-2 gap-3 sm:max-w-2xl sm:grid-cols-3">
            {[
              { label: "Open pipeline", value: "$4.82M", tone: "text-slate-900 dark:text-white" },
              { label: "Win rate", value: "31.4%", tone: "text-emerald-600 dark:text-emerald-400" },
              { label: "Stalled deals", value: "17", tone: "text-amber-600 dark:text-amber-400" },
            ].map((stat) => (
              <div
                key={stat.label}
                className="rounded-2xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900/60"
              >
                <p className="text-xs font-medium text-slate-500 dark:text-slate-400">{stat.label}</p>
                <p className={`mt-1 text-xl font-semibold tabular-nums ${stat.tone}`}>{stat.value}</p>
              </div>
            ))}
          </div>
        </div>
      </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