Web InnoventixFreeCode

Dark Sidebar

Original · free

dark themed dashboard sidebar

byWeb InnoventixReact + Tailwind
sidedarksidebars
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/side-dark.json
side-dark.tsx
"use client";

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

type IconProps = { className?: string };
type Icon = (props: IconProps) => ReactNode;

type NavChild = { id: string; label: string };
type NavItem = {
  id: string;
  label: string;
  icon: Icon;
  badge?: number;
  children?: NavChild[];
};

function HomeIcon({ 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="M3 10.5 12 3l9 7.5" />
      <path d="M5 9.5V20a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V9.5" />
    </svg>
  );
}

function ChartIcon({ 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="M4 20h16" />
      <rect x="5" y="12" width="3" height="6" rx="1" />
      <rect x="10.5" y="8" width="3" height="10" rx="1" />
      <rect x="16" y="4" width="3" height="14" rx="1" />
    </svg>
  );
}

function UsersIcon({ 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="9" cy="8" r="3.2" />
      <path d="M3.5 19a5.5 5.5 0 0 1 11 0" />
      <path d="M16 5.2a3.2 3.2 0 0 1 0 6" />
      <path d="M17 14.2A5.5 5.5 0 0 1 20.5 19" />
    </svg>
  );
}

function RevenueIcon({ 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="9" />
      <path d="M14.5 9a2.5 2.5 0 0 0-2.5-1.5c-1.4 0-2.5.8-2.5 2s1 1.6 2.5 2 2.5.9 2.5 2-1.1 2-2.5 2A2.5 2.5 0 0 1 9.5 15" />
      <path d="M12 6v1.5M12 16.5V18" />
    </svg>
  );
}

function FolderIcon({ 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="M3 7a2 2 0 0 1 2-2h3.6a2 2 0 0 1 1.5.7L11 7h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" />
    </svg>
  );
}

function ReportIcon({ 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="M6 3h8l4 4v14a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z" />
      <path d="M13 3v5h5" />
      <path d="M8.5 13h7M8.5 16.5h7" />
    </svg>
  );
}

function GearIcon({ 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="M12 2.5v2.2M12 19.3v2.2M4.2 7l1.9 1.1M17.9 15.9 19.8 17M2.5 12h2.2M19.3 12h2.2M4.2 17l1.9-1.1M17.9 8.1 19.8 7" />
    </svg>
  );
}

function HelpIcon({ 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="9" />
      <path d="M9.6 9.4a2.4 2.4 0 0 1 4.7.6c0 1.6-2.3 2-2.3 3.4" />
      <path d="M12 16.6h.01" />
    </svg>
  );
}

function SearchIcon({ 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="11" cy="11" r="6.5" />
      <path d="m20 20-3.4-3.4" />
    </svg>
  );
}

function ChevronIcon({ 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="m9 6 6 6-6 6" />
    </svg>
  );
}

function PanelIcon({ 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">
      <rect x="3.5" y="4.5" width="17" height="15" rx="2.5" />
      <path d="M9.5 4.5v15" />
    </svg>
  );
}

function BellIcon({ 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="M6 9a6 6 0 0 1 12 0c0 5 1.5 6 1.5 6H4.5S6 14 6 9Z" />
      <path d="M10 19a2 2 0 0 0 4 0" />
    </svg>
  );
}

function LogoutIcon({ 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="M15 4h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-3" />
      <path d="M10 12H3.5M6 8.5 2.5 12 6 15.5" />
    </svg>
  );
}

const PRIMARY_NAV: NavItem[] = [
  { id: "overview", label: "Overview", icon: HomeIcon },
  {
    id: "analytics",
    label: "Analytics",
    icon: ChartIcon,
    children: [
      { id: "traffic", label: "Traffic" },
      { id: "funnels", label: "Funnels" },
      { id: "cohorts", label: "Cohorts" },
    ],
  },
  { id: "customers", label: "Customers", icon: UsersIcon, badge: 12 },
  { id: "revenue", label: "Revenue", icon: RevenueIcon },
  {
    id: "projects",
    label: "Projects",
    icon: FolderIcon,
    children: [
      { id: "active", label: "Active" },
      { id: "archived", label: "Archived" },
    ],
  },
  { id: "reports", label: "Reports", icon: ReportIcon, badge: 3 },
];

const SECONDARY_NAV: NavItem[] = [
  { id: "settings", label: "Settings", icon: GearIcon },
  { id: "help", label: "Help & docs", icon: HelpIcon },
];

const LABELS: Record<string, string> = (() => {
  const map: Record<string, string> = {};
  for (const group of [PRIMARY_NAV, SECONDARY_NAV]) {
    for (const item of group) {
      map[item.id] = item.label;
      for (const child of item.children ?? []) {
        map[child.id] = child.label;
      }
    }
  }
  return map;
})();

function filterNav(items: NavItem[], q: string): NavItem[] {
  if (!q) return items;
  const out: NavItem[] = [];
  for (const item of items) {
    const selfMatch = item.label.toLowerCase().includes(q);
    const kids = (item.children ?? []).filter((c) =>
      c.label.toLowerCase().includes(q),
    );
    if (selfMatch) {
      out.push(item);
    } else if (kids.length > 0) {
      out.push({ ...item, children: kids });
    }
  }
  return out;
}

export default function SideDark() {
  const reduce = useReducedMotion();
  const baseId = useId();
  const searchId = `${baseId}-search`;

  const [collapsed, setCollapsed] = useState(false);
  const [query, setQuery] = useState("");
  const [activeId, setActiveId] = useState("overview");
  const [expanded, setExpanded] = useState<Record<string, boolean>>({
    analytics: true,
  });
  const [menuOpen, setMenuOpen] = useState(false);

  const menuRef = useRef<HTMLDivElement | null>(null);

  const q = query.trim().toLowerCase();
  const filteredPrimary = useMemo(() => filterNav(PRIMARY_NAV, q), [q]);
  const filteredSecondary = useMemo(() => filterNav(SECONDARY_NAV, q), [q]);

  const resultCount = useMemo(() => {
    if (!q) return 0;
    let n = 0;
    for (const item of [...filteredPrimary, ...filteredSecondary]) {
      n += item.children ? item.children.length : 1;
    }
    return n;
  }, [q, filteredPrimary, filteredSecondary]);

  useEffect(() => {
    if (!menuOpen) return;
    const onDown = (e: MouseEvent) => {
      if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
        setMenuOpen(false);
      }
    };
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setMenuOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDown);
      document.removeEventListener("keydown", onKey);
    };
  }, [menuOpen]);

  const isExpanded = (id: string) => (q ? true : Boolean(expanded[id]));

  const isSectionActive = (item: NavItem) =>
    item.id === activeId ||
    (item.children ?? []).some((c) => c.id === activeId);

  const handleParentClick = (item: NavItem) => {
    if (collapsed) {
      setCollapsed(false);
      setExpanded((prev) => ({ ...prev, [item.id]: true }));
      return;
    }
    setExpanded((prev) => ({ ...prev, [item.id]: !prev[item.id] }));
  };

  const springy = reduce
    ? { duration: 0 }
    : { type: "spring" as const, stiffness: 320, damping: 34 };
  const collapseTx = reduce ? { duration: 0 } : { duration: 0.28, ease: "easeInOut" as const };

  const activeLabel = LABELS[activeId] ?? "Overview";

  const renderItem = (item: NavItem) => {
    const Ico = item.icon;
    const hasChildren = Boolean(item.children && item.children.length > 0);
    const open = hasChildren && isExpanded(item.id);
    const sectionActive = isSectionActive(item);
    const leafActive = !hasChildren && item.id === activeId;
    const subId = `${baseId}-sub-${item.id}`;

    return (
      <li key={item.id}>
        <button
          type="button"
          onClick={() =>
            hasChildren ? handleParentClick(item) : setActiveId(item.id)
          }
          aria-current={leafActive ? "page" : undefined}
          aria-expanded={hasChildren ? open : undefined}
          aria-controls={hasChildren && open ? subId : undefined}
          title={collapsed ? item.label : undefined}
          className={[
            "group relative flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium outline-none transition-colors",
            "focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950",
            collapsed ? "justify-center" : "",
            sectionActive
              ? "bg-white/10 text-white"
              : "text-zinc-400 hover:bg-white/5 hover:text-zinc-100",
          ].join(" ")}
        >
          {leafActive ? (
            <span
              aria-hidden="true"
              className="absolute left-0 top-1/2 h-5 w-1 -translate-y-1/2 rounded-r-full bg-gradient-to-b from-indigo-400 to-violet-500"
            />
          ) : null}
          <Ico className="h-5 w-5 shrink-0" />
          {!collapsed && (
            <span className="flex-1 truncate text-left">{item.label}</span>
          )}
          {!collapsed && typeof item.badge === "number" && (
            <span className="rounded-full bg-indigo-500/20 px-2 py-0.5 text-[11px] font-semibold text-indigo-300 ring-1 ring-inset ring-indigo-400/30">
              {item.badge}
            </span>
          )}
          {!collapsed && hasChildren && (
            <ChevronIcon
              className={[
                "h-4 w-4 shrink-0 text-zinc-500 transition-transform duration-200",
                open ? "rotate-90" : "",
              ].join(" ")}
            />
          )}
        </button>

        <AnimatePresence initial={false}>
          {hasChildren && open && !collapsed && (
            <motion.ul
              id={subId}
              initial={{ height: 0, opacity: 0 }}
              animate={{ height: "auto", opacity: 1 }}
              exit={{ height: 0, opacity: 0 }}
              transition={collapseTx}
              className="overflow-hidden"
            >
              <div className="ml-5 mt-1 flex flex-col gap-0.5 border-l border-white/10 pl-3">
                {(item.children ?? []).map((child) => {
                  const childActive = child.id === activeId;
                  return (
                    <li key={child.id}>
                      <button
                        type="button"
                        onClick={() => setActiveId(child.id)}
                        aria-current={childActive ? "page" : undefined}
                        className={[
                          "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm outline-none transition-colors",
                          "focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950",
                          childActive
                            ? "font-medium text-white"
                            : "text-zinc-400 hover:text-zinc-100",
                        ].join(" ")}
                      >
                        <span
                          aria-hidden="true"
                          className={[
                            "h-1.5 w-1.5 shrink-0 rounded-full transition-colors",
                            childActive
                              ? "bg-indigo-400"
                              : "bg-zinc-600 group-hover:bg-zinc-500",
                          ].join(" ")}
                        />
                        <span className="truncate">{child.label}</span>
                      </button>
                    </li>
                  );
                })}
              </div>
            </motion.ul>
          )}
        </AnimatePresence>
      </li>
    );
  };

  return (
    <section className="relative w-full bg-slate-100 px-4 py-16 dark:bg-slate-950 sm:py-20">
      <style>{`
        @keyframes sidedark-ping {
          0% { transform: scale(1); opacity: 0.75; }
          70%, 100% { transform: scale(2.4); opacity: 0; }
        }
        @keyframes sidedark-sheen {
          0% { background-position: -140% 0; }
          100% { background-position: 240% 0; }
        }
        .sidedark-anim-ping { animation: sidedark-ping 2.2s cubic-bezier(0,0,0.2,1) infinite; }
        .sidedark-anim-sheen {
          background-image: linear-gradient(110deg, transparent 20%, rgba(255,255,255,0.5) 50%, transparent 80%);
          background-size: 220% 100%;
          animation: sidedark-sheen 4.5s ease-in-out infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .sidedark-anim-ping, .sidedark-anim-sheen { animation: none !important; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <div className="mb-6 text-center">
          <p className="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-500 dark:text-indigo-400">
            Component
          </p>
          <h2 className="mt-2 text-2xl font-bold tracking-tight text-slate-900 dark:text-slate-50 sm:text-3xl">
            Dark dashboard sidebar
          </h2>
        </div>

        <div className="flex min-h-[640px] overflow-hidden rounded-3xl border border-slate-300/60 bg-white shadow-2xl shadow-slate-900/10 dark:border-white/10 dark:bg-zinc-900 dark:shadow-black/40">
          {/* Sidebar (always dark themed) */}
          <motion.aside
            initial={false}
            animate={{ width: collapsed ? 78 : 280 }}
            transition={collapseTx}
            aria-label="Primary"
            className="relative flex shrink-0 flex-col overflow-hidden border-r border-white/10 bg-gradient-to-b from-zinc-950 to-zinc-900 text-zinc-100"
          >
            {/* Brand + collapse */}
            <div className="flex items-center gap-3 px-4 py-5">
              <span className="relative grid h-9 w-9 shrink-0 place-items-center overflow-hidden rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 text-white shadow-lg shadow-indigo-900/40">
                <span className="absolute inset-0 sidedark-anim-sheen" aria-hidden="true" />
                <svg viewBox="0 0 24 24" fill="none" className="relative h-5 w-5" aria-hidden="true">
                  <path d="M12 3 4 7.5v9L12 21l8-4.5v-9L12 3Z" stroke="currentColor" strokeWidth={1.6} strokeLinejoin="round" />
                  <path d="M12 12v9M12 12 4 7.5M12 12l8-4.5" stroke="currentColor" strokeWidth={1.6} strokeLinejoin="round" />
                </svg>
              </span>
              {!collapsed && (
                <div className="min-w-0 flex-1">
                  <p className="truncate text-sm font-semibold text-white">Halyard</p>
                  <p className="truncate text-xs text-zinc-500">Analytics workspace</p>
                </div>
              )}
              <button
                type="button"
                onClick={() => setCollapsed((v) => !v)}
                aria-pressed={collapsed}
                aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
                title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
                className={[
                  "grid h-8 w-8 shrink-0 place-items-center rounded-lg text-zinc-400 outline-none transition-colors hover:bg-white/10 hover:text-white",
                  "focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950",
                  collapsed ? "mx-auto" : "",
                ].join(" ")}
              >
                <PanelIcon className={["h-5 w-5 transition-transform", collapsed ? "rotate-180" : ""].join(" ")} />
              </button>
            </div>

            {/* Search */}
            {!collapsed && (
              <div className="px-4 pb-2">
                <label htmlFor={searchId} className="sr-only">
                  Search navigation
                </label>
                <div className="relative">
                  <SearchIcon className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-zinc-500" />
                  <input
                    id={searchId}
                    type="text"
                    role="searchbox"
                    value={query}
                    onChange={(e) => setQuery(e.target.value)}
                    placeholder="Search…"
                    className="w-full rounded-xl border border-white/10 bg-white/5 py-2 pl-9 pr-3 text-sm text-zinc-100 placeholder-zinc-500 outline-none transition-colors focus:border-indigo-400/60 focus:bg-white/10 focus-visible:ring-2 focus-visible:ring-indigo-400/50"
                  />
                </div>
                <p className="sr-only" aria-live="polite">
                  {q ? `${resultCount} navigation result${resultCount === 1 ? "" : "s"}` : ""}
                </p>
              </div>
            )}

            {/* Nav */}
            <nav className="flex-1 overflow-y-auto px-3 py-2">
              {!collapsed && (
                <p className="px-3 pb-1.5 pt-2 text-[11px] font-semibold uppercase tracking-wider text-zinc-600">
                  Workspace
                </p>
              )}
              <ul className="flex flex-col gap-0.5">
                {filteredPrimary.map(renderItem)}
                {q && filteredPrimary.length === 0 && !collapsed && (
                  <li className="px-3 py-2 text-sm text-zinc-500">No matches</li>
                )}
              </ul>

              <div className="my-3 border-t border-white/10" />

              {!collapsed && (
                <p className="px-3 pb-1.5 pt-1 text-[11px] font-semibold uppercase tracking-wider text-zinc-600">
                  System
                </p>
              )}
              <ul className="flex flex-col gap-0.5">
                {filteredSecondary.map(renderItem)}
              </ul>
            </nav>

            {/* Upgrade card */}
            {!collapsed && (
              <div className="px-4 pb-3">
                <div className="rounded-2xl border border-white/10 bg-white/5 p-3.5">
                  <div className="flex items-center gap-2">
                    <span className="relative flex h-2 w-2">
                      <span className="sidedark-anim-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400" aria-hidden="true" />
                      <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-400" />
                    </span>
                    <p className="text-xs font-semibold text-white">Trial · 6 days left</p>
                  </div>
                  <p className="mt-1.5 text-xs leading-relaxed text-zinc-400">
                    Unlock unlimited seats and 24-month data retention.
                  </p>
                  <button
                    type="button"
                    className="mt-3 w-full rounded-lg bg-gradient-to-r from-indigo-500 to-violet-600 py-2 text-xs font-semibold text-white outline-none transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-indigo-300 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950"
                  >
                    Upgrade plan
                  </button>
                </div>
              </div>
            )}

            {/* User / account menu */}
            <div ref={menuRef} className="relative border-t border-white/10 p-3">
              <AnimatePresence>
                {menuOpen && !collapsed && (
                  <motion.div
                    initial={{ opacity: 0, y: 8, scale: 0.98 }}
                    animate={{ opacity: 1, y: 0, scale: 1 }}
                    exit={{ opacity: 0, y: 8, scale: 0.98 }}
                    transition={springy}
                    role="menu"
                    aria-label="Account"
                    className="absolute bottom-[calc(100%-0.25rem)] left-3 right-3 origin-bottom rounded-xl border border-white/10 bg-zinc-800 p-1.5 shadow-2xl shadow-black/50"
                  >
                    <button
                      type="button"
                      role="menuitem"
                      onClick={() => setMenuOpen(false)}
                      className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-zinc-200 outline-none transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-indigo-400"
                    >
                      <GearIcon className="h-4 w-4 text-zinc-400" />
                      Account settings
                    </button>
                    <button
                      type="button"
                      role="menuitem"
                      onClick={() => setMenuOpen(false)}
                      className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-zinc-200 outline-none transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-indigo-400"
                    >
                      <BellIcon className="h-4 w-4 text-zinc-400" />
                      Notifications
                    </button>
                    <div className="my-1 border-t border-white/10" />
                    <button
                      type="button"
                      role="menuitem"
                      onClick={() => setMenuOpen(false)}
                      className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-rose-300 outline-none transition-colors hover:bg-rose-500/10 focus-visible:ring-2 focus-visible:ring-rose-400"
                    >
                      <LogoutIcon className="h-4 w-4" />
                      Sign out
                    </button>
                  </motion.div>
                )}
              </AnimatePresence>

              <button
                type="button"
                onClick={() => setMenuOpen((v) => !v)}
                aria-haspopup="menu"
                aria-expanded={menuOpen}
                title={collapsed ? "Priya Nair" : undefined}
                className={[
                  "flex w-full items-center gap-3 rounded-xl px-2 py-2 text-left outline-none transition-colors hover:bg-white/5",
                  "focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950",
                  collapsed ? "justify-center" : "",
                ].join(" ")}
              >
                <span className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-gradient-to-br from-emerald-400 to-sky-500 text-sm font-bold text-zinc-900">
                  PN
                </span>
                {!collapsed && (
                  <>
                    <span className="min-w-0 flex-1">
                      <span className="block truncate text-sm font-semibold text-white">Priya Nair</span>
                      <span className="block truncate text-xs text-zinc-500">Product Lead</span>
                    </span>
                    <ChevronIcon
                      className={["h-4 w-4 shrink-0 text-zinc-500 transition-transform", menuOpen ? "-rotate-90" : "rotate-90"].join(" ")}
                    />
                  </>
                )}
              </button>
            </div>
          </motion.aside>

          {/* Content preview (adapts to light + dark) */}
          <div className="flex min-w-0 flex-1 flex-col bg-slate-50 dark:bg-zinc-950">
            <header className="flex flex-wrap items-center justify-between gap-3 border-b border-slate-200 px-6 py-5 dark:border-white/10">
              <div className="min-w-0">
                <h3 className="truncate text-lg font-semibold text-slate-900 dark:text-slate-50">
                  {activeLabel}
                </h3>
                <p className="text-sm text-slate-500 dark:text-zinc-400">
                  Tuesday, 16 July · Last synced 4 minutes ago
                </p>
              </div>
              <div className="flex items-center gap-2">
                <button
                  type="button"
                  aria-label="Notifications"
                  className="relative grid h-9 w-9 place-items-center rounded-xl border border-slate-200 text-slate-600 outline-none transition-colors hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-indigo-400 dark:border-white/10 dark:text-zinc-300 dark:hover:bg-white/5"
                >
                  <BellIcon className="h-5 w-5" />
                  <span className="absolute right-2 top-2 h-2 w-2 rounded-full bg-rose-500 ring-2 ring-slate-50 dark:ring-zinc-950" />
                </button>
                <button
                  type="button"
                  className="rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white outline-none transition-colors hover:bg-slate-700 focus-visible:ring-2 focus-visible:ring-indigo-400 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200"
                >
                  New report
                </button>
              </div>
            </header>

            <div className="grid gap-4 p-6 sm:grid-cols-2 lg:grid-cols-3">
              {[
                { k: "Monthly revenue", v: "$48,210", d: "+12.4%", up: true },
                { k: "Active users", v: "12,480", d: "+3.1%", up: true },
                { k: "Churn rate", v: "1.8%", d: "-0.4%", up: false },
              ].map((s) => (
                <div
                  key={s.k}
                  className="rounded-2xl border border-slate-200 bg-white p-5 dark:border-white/10 dark:bg-zinc-900"
                >
                  <p className="text-sm text-slate-500 dark:text-zinc-400">{s.k}</p>
                  <div className="mt-2 flex items-end justify-between">
                    <p className="text-2xl font-bold tracking-tight text-slate-900 dark:text-slate-50">
                      {s.v}
                    </p>
                    <span
                      className={[
                        "rounded-full px-2 py-0.5 text-xs font-semibold",
                        s.up
                          ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400"
                          : "bg-rose-100 text-rose-700 dark:bg-rose-500/15 dark:text-rose-400",
                      ].join(" ")}
                    >
                      {s.d}
                    </span>
                  </div>
                </div>
              ))}
            </div>

            <div className="px-6 pb-6">
              <div className="rounded-2xl border border-slate-200 bg-white p-5 dark:border-white/10 dark:bg-zinc-900">
                <div className="mb-4 flex items-center justify-between">
                  <p className="text-sm font-semibold text-slate-900 dark:text-slate-50">
                    Weekly signups
                  </p>
                  <p className="text-xs text-slate-500 dark:text-zinc-400">Last 7 days</p>
                </div>
                <div className="flex h-32 items-end gap-2" aria-hidden="true">
                  {[42, 58, 51, 74, 63, 88, 96].map((h, i) => (
                    <div
                      key={i}
                      className="flex-1 rounded-t-md bg-gradient-to-t from-indigo-500/80 to-violet-500/80"
                      style={{ height: `${h}%` }}
                    />
                  ))}
                </div>
                <div className="mt-2 flex gap-2 text-[11px] text-slate-400 dark:text-zinc-500">
                  {["M", "T", "W", "T", "F", "S", "S"].map((d, i) => (
                    <span key={i} className="flex-1 text-center">{d}</span>
                  ))}
                </div>
              </div>
            </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 →