App Navbar
Original · freeapp top bar with avatar and actions
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"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
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

