Web InnoventixFreeCode

Sidebar Toggle Navbar

Original · free

navbar with a sidebar toggle

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

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

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

type IconProps = { className?: string };

function IconMenu({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.9} strokeLinecap="round" aria-hidden="true" focusable="false">
      <path d="M4 7h16M4 12h16M4 17h10" />
    </svg>
  );
}

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

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

function IconBell({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M6 9a6 6 0 0 1 12 0c0 4 1.2 5.5 2 6.5H4c.8-1 2-2.5 2-6.5Z" />
      <path d="M10 19a2 2 0 0 0 4 0" />
    </svg>
  );
}

function IconGrid({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinejoin="round" aria-hidden="true" focusable="false">
      <rect x="3.5" y="3.5" width="7" height="7" rx="1.6" />
      <rect x="13.5" y="3.5" width="7" height="7" rx="1.6" />
      <rect x="3.5" y="13.5" width="7" height="7" rx="1.6" />
      <rect x="13.5" y="13.5" width="7" height="7" rx="1.6" />
    </svg>
  );
}

function IconFolder({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M3.5 6.5A1.5 1.5 0 0 1 5 5h4l2 2.5h6.5A1.5 1.5 0 0 1 19 9v8.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 3 17.5V6.5Z" />
    </svg>
  );
}

function IconChart({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M4 20V10M10 20V4M16 20v-7M22 20H2" />
    </svg>
  );
}

function IconCalendar({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <rect x="3.5" y="5" width="17" height="15" rx="2" />
      <path d="M3.5 9.5h17M8 3.5v3M16 3.5v3" />
    </svg>
  );
}

function IconInbox({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M3.5 13 6 5.5A1.5 1.5 0 0 1 7.4 4.5h9.2A1.5 1.5 0 0 1 18 5.5L20.5 13v4.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2V13Z" />
      <path d="M3.5 13H8a1 1 0 0 1 1 1 3 3 0 0 0 6 0 1 1 0 0 1 1-1h4.5" />
    </svg>
  );
}

function IconUsers({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <circle cx="9" cy="8" r="3.2" />
      <path d="M3.5 19a5.5 5.5 0 0 1 11 0M16 5.2a3.2 3.2 0 0 1 0 5.6M17 19a5.5 5.5 0 0 0-3-4.9" />
    </svg>
  );
}

function IconSettings({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M5 7h9M18 7h1M5 12h1M10 12h9M5 17h6M15 17h4" />
      <circle cx="16" cy="7" r="2" />
      <circle cx="8" cy="12" r="2" />
      <circle cx="13" cy="17" r="2" />
    </svg>
  );
}

function IconBolt({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M13 3 5 13h5l-1 8 8-10h-5l1-8Z" />
    </svg>
  );
}

function IconArrow({ className = "h-4 w-4" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.9} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
      <path d="M5 12h14M13 6l6 6-6 6" />
    </svg>
  );
}

function BrandMark({ className = "h-5 w-5" }: IconProps) {
  return (
    <svg className={className} viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false">
      <path d="M4 15c2.5 0 3.2-6 5.5-6S12 15 14.5 15 17 9 20 9" stroke="currentColor" strokeWidth={2.1} strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

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

type NavItem = { id: string; label: string; icon: ReactNode; badge?: string };
type NavGroup = { heading: string; items: NavItem[] };

const NAV: NavGroup[] = [
  {
    heading: "Workspace",
    items: [
      { id: "overview", label: "Overview", icon: <IconGrid /> },
      { id: "projects", label: "Projects", icon: <IconFolder />, badge: "12" },
      { id: "analytics", label: "Analytics", icon: <IconChart /> },
      { id: "schedule", label: "Schedule", icon: <IconCalendar /> },
    ],
  },
  {
    heading: "Communication",
    items: [
      { id: "inbox", label: "Inbox", icon: <IconInbox />, badge: "3" },
      { id: "team", label: "Team", icon: <IconUsers /> },
    ],
  },
  {
    heading: "Account",
    items: [{ id: "settings", label: "Settings", icon: <IconSettings /> }],
  },
];

const STATS: { label: string; value: string; delta: string; good: boolean }[] = [
  { label: "Active sprints", value: "7", delta: "+2 this week", good: true },
  { label: "Avg. cycle time", value: "3.2d", delta: "-0.4d", good: true },
  { label: "Merged PRs", value: "128", delta: "+31", good: true },
  { label: "Open issues", value: "42", delta: "+5", good: false },
];

const ACTIVITY: { who: string; ring: string; action: string; target: string; time: string }[] = [
  { who: "AO", ring: "bg-emerald-500", action: "merged", target: "feat/auth-rate-limit", time: "12m" },
  { who: "DM", ring: "bg-sky-500", action: "opened", target: "Fix flaky billing e2e test", time: "34m" },
  { who: "PN", ring: "bg-violet-500", action: "commented on", target: "RFC: event-sourced audit log", time: "1h" },
  { who: "JB", ring: "bg-amber-500", action: "closed", target: "Sprint 24 retro follow-ups", time: "2h" },
];

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

function getFocusable(container: HTMLElement | null): HTMLElement[] {
  if (!container) return [];
  const nodes = container.querySelectorAll<HTMLElement>(
    'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'
  );
  return Array.from(nodes).filter((el) => el.offsetParent !== null || el === document.activeElement);
}

/* -------------------------------- component -------------------------------- */

export default function NavxSidebarToggle() {
  const reduce = useReducedMotion();
  const [open, setOpen] = useState(false);
  const [active, setActive] = useState("overview");

  const uid = useId();
  const drawerId = `${uid}-drawer`;
  const titleId = `${uid}-title`;
  const searchId = `${uid}-search`;

  const toggleRef = useRef<HTMLButtonElement>(null);
  const closeRef = useRef<HTMLButtonElement>(null);
  const drawerRef = useRef<HTMLElement>(null);

  const close = useCallback(() => {
    setOpen(false);
    toggleRef.current?.focus();
  }, []);

  // Move focus into the drawer once it is presented.
  useEffect(() => {
    if (!open) return;
    const t = window.setTimeout(() => closeRef.current?.focus(), 20);
    return () => window.clearTimeout(t);
  }, [open]);

  // Global Escape safety net.
  useEffect(() => {
    if (!open) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") close();
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [open, close]);

  const handleDrawerKeyDown = useCallback((e: ReactKeyboardEvent<HTMLElement>) => {
    if (e.key !== "Tab") return;
    const focusables = getFocusable(drawerRef.current);
    if (focusables.length === 0) return;
    const first = focusables[0];
    const last = focusables[focusables.length - 1];
    const activeEl = document.activeElement;
    if (e.shiftKey && activeEl === first) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey && activeEl === last) {
      e.preventDefault();
      first.focus();
    }
  }, []);

  const selectItem = useCallback((id: string) => {
    setActive(id);
    setOpen(false);
    toggleRef.current?.focus();
  }, []);

  const activeLabel =
    NAV.flatMap((g) => g.items).find((i) => i.id === active)?.label ?? "Overview";

  const keyframes = `
    @keyframes navxsb-ping { 0% { transform: scale(1); opacity: .65; } 75%, 100% { transform: scale(2.4); opacity: 0; } }
    @keyframes navxsb-float { 0%, 100% { transform: translate3d(0,0,0); } 50% { transform: translate3d(0,-14px,0); } }
    .navxsb-ping { animation: navxsb-ping 1.9s cubic-bezier(0,0,.2,1) infinite; }
    .navxsb-float-a { animation: navxsb-float 11s ease-in-out infinite; }
    .navxsb-float-b { animation: navxsb-float 14s ease-in-out infinite reverse; }
    @media (prefers-reduced-motion: reduce) {
      .navxsb-ping, .navxsb-float-a, .navxsb-float-b { animation: none !important; }
    }
  `;

  return (
    <section className="relative w-full bg-slate-100 px-4 py-16 text-slate-900 sm:px-6 sm:py-20 dark:bg-slate-950 dark:text-slate-100">
      <style>{keyframes}</style>

      <div className="mx-auto max-w-6xl">
        <div
          className="relative flex min-h-[640px] flex-col overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-[0_30px_80px_-40px_rgba(15,23,42,0.45)] dark:border-slate-800 dark:bg-slate-900 dark:shadow-[0_30px_90px_-40px_rgba(0,0,0,0.9)]"
        >
          {/* ------------------------------ navbar ------------------------------ */}
          <header className="relative z-20 flex items-center gap-2 border-b border-slate-200 bg-white/85 px-3 py-3 backdrop-blur-md sm:gap-3 sm:px-5 dark:border-slate-800 dark:bg-slate-900/80">
            <button
              ref={toggleRef}
              type="button"
              onClick={() => setOpen((v) => !v)}
              aria-expanded={open}
              aria-controls={drawerId}
              aria-label={open ? "Close navigation sidebar" : "Open navigation sidebar"}
              className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-slate-200 text-slate-700 transition-colors hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-900"
            >
              <IconMenu />
            </button>

            <div className="flex min-w-0 items-center gap-2.5">
              <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 text-white shadow-inner">
                <BrandMark className="h-5 w-5" />
              </span>
              <div className="min-w-0 leading-tight">
                <p className="truncate text-sm font-semibold tracking-tight text-slate-900 dark:text-white">
                  Cadence
                </p>
                <p className="truncate text-[11px] text-slate-500 dark:text-slate-400">
                  Platform team
                </p>
              </div>
            </div>

            <div className="ml-auto flex items-center gap-2 sm:gap-3">
              <div className="relative hidden md:block">
                <label htmlFor={searchId} className="sr-only">
                  Search projects, issues and people
                </label>
                <span className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 dark:text-slate-500">
                  <IconSearch className="h-4 w-4" />
                </span>
                <input
                  id={searchId}
                  type="search"
                  placeholder="Search workspace…"
                  className="h-10 w-56 rounded-xl border border-slate-200 bg-slate-50 pl-9 pr-3 text-sm text-slate-700 placeholder:text-slate-400 transition-colors focus:border-indigo-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white lg:w-72 dark:border-slate-700 dark:bg-slate-800/60 dark:text-slate-200 dark:placeholder:text-slate-500 dark:focus-visible:ring-offset-slate-900"
                />
              </div>

              <button
                type="button"
                aria-label="Notifications, 3 unread"
                className="relative inline-flex h-10 w-10 items-center justify-center rounded-xl border border-slate-200 text-slate-700 transition-colors hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-900"
              >
                <IconBell />
                <span className="absolute right-2.5 top-2.5 flex h-2.5 w-2.5">
                  <span className="navxsb-ping absolute inline-flex h-full w-full rounded-full bg-rose-400" />
                  <span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-rose-500 ring-2 ring-white dark:ring-slate-900" />
                </span>
              </button>

              <button
                type="button"
                aria-label="Account menu for Salman Riaz"
                className="inline-flex h-10 items-center gap-2 rounded-xl border border-slate-200 pl-1 pr-1 transition-colors hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white sm:pr-2.5 dark:border-slate-700 dark:hover:bg-slate-800 dark:focus-visible:ring-offset-slate-900"
              >
                <span className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-400 to-sky-500 text-xs font-semibold text-white">
                  SR
                </span>
                <span className="hidden text-sm font-medium text-slate-700 sm:inline dark:text-slate-200">
                  Salman
                </span>
              </button>
            </div>
          </header>

          {/* ------------------------------ content ----------------------------- */}
          <main className="relative z-0 flex-1 overflow-hidden p-6 sm:p-8">
            <div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
              <div className="navxsb-float-a absolute -left-16 -top-20 h-64 w-64 rounded-full bg-indigo-400/20 blur-3xl dark:bg-indigo-500/15" />
              <div className="navxsb-float-b absolute -bottom-24 right-0 h-72 w-72 rounded-full bg-violet-400/20 blur-3xl dark:bg-violet-600/15" />
            </div>

            <div className="relative">
              <div className="flex flex-wrap items-end justify-between gap-3">
                <div>
                  <p className="text-xs font-medium uppercase tracking-[0.18em] text-indigo-600 dark:text-indigo-400">
                    {activeLabel}
                  </p>
                  <h1 className="mt-1 text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
                    Good afternoon, Salman
                  </h1>
                  <p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
                    Sprint 24 wraps Friday. You have 4 reviews waiting.
                  </p>
                </div>
                <button
                  type="button"
                  className="inline-flex items-center gap-2 rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200 dark:focus-visible:ring-offset-slate-900"
                >
                  New project
                  <IconArrow className="h-4 w-4" />
                </button>
              </div>

              <div className="mt-6 grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-4">
                {STATS.map((s) => (
                  <div
                    key={s.label}
                    className="rounded-2xl border border-slate-200 bg-slate-50/70 p-4 dark:border-slate-800 dark:bg-slate-800/40"
                  >
                    <p className="text-xs font-medium text-slate-500 dark:text-slate-400">{s.label}</p>
                    <p className="mt-2 text-2xl font-semibold tracking-tight text-slate-900 dark:text-white">
                      {s.value}
                    </p>
                    <p
                      className={`mt-1 text-xs font-medium ${
                        s.good
                          ? "text-emerald-600 dark:text-emerald-400"
                          : "text-rose-600 dark:text-rose-400"
                      }`}
                    >
                      {s.delta}
                    </p>
                  </div>
                ))}
              </div>

              <div className="mt-4 rounded-2xl border border-slate-200 bg-white p-5 dark:border-slate-800 dark:bg-slate-900/60">
                <div className="flex items-center justify-between">
                  <h2 className="text-sm font-semibold text-slate-900 dark:text-white">Recent activity</h2>
                  <span className="text-xs text-slate-400 dark:text-slate-500">Today</span>
                </div>
                <ul className="mt-3 divide-y divide-slate-100 dark:divide-slate-800">
                  {ACTIVITY.map((a) => (
                    <li key={a.target} className="flex items-center gap-3 py-2.5">
                      <span
                        className={`inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold text-white ${a.ring}`}
                      >
                        {a.who}
                      </span>
                      <p className="min-w-0 flex-1 truncate text-sm text-slate-600 dark:text-slate-300">
                        <span className="font-medium text-slate-900 dark:text-white">{a.who === "AO" ? "Amara" : a.who === "DM" ? "Diego" : a.who === "PN" ? "Priya" : "Jonas"}</span>{" "}
                        {a.action}{" "}
                        <span className="font-medium text-slate-800 dark:text-slate-100">{a.target}</span>
                      </p>
                      <span className="shrink-0 text-xs text-slate-400 dark:text-slate-500">{a.time}</span>
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          </main>

          {/* --------------------------- drawer + scrim ------------------------- */}
          <AnimatePresence>
            {open && (
              <motion.div
                key="navxsb-scrim"
                className="absolute inset-0 z-30 bg-slate-900/45 backdrop-blur-[2px] dark:bg-black/60"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                transition={{ duration: reduce ? 0 : 0.22, ease: "easeOut" }}
                onClick={close}
                aria-hidden="true"
              />
            )}

            {open && (
              <motion.aside
                key="navxsb-drawer"
                ref={drawerRef}
                id={drawerId}
                role="dialog"
                aria-modal="true"
                aria-labelledby={titleId}
                onKeyDown={handleDrawerKeyDown}
                className="absolute inset-y-0 left-0 z-40 flex w-72 max-w-[85%] flex-col border-r border-slate-200 bg-white shadow-2xl dark:border-slate-800 dark:bg-slate-900"
                initial={{ x: "-100%" }}
                animate={{ x: 0 }}
                exit={{ x: "-100%" }}
                transition={
                  reduce ? { duration: 0 } : { type: "spring", stiffness: 340, damping: 34 }
                }
              >
                <div className="flex items-center justify-between border-b border-slate-200 px-4 py-4 dark:border-slate-800">
                  <div className="flex items-center gap-2.5">
                    <span className="inline-flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 text-white">
                      <BrandMark className="h-5 w-5" />
                    </span>
                    <span id={titleId} className="text-sm font-semibold tracking-tight text-slate-900 dark:text-white">
                      Cadence
                    </span>
                  </div>
                  <button
                    ref={closeRef}
                    type="button"
                    onClick={close}
                    aria-label="Close navigation sidebar"
                    className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white dark:focus-visible:ring-offset-slate-900"
                  >
                    <IconClose />
                  </button>
                </div>

                <nav aria-label="Primary" className="flex-1 overflow-y-auto px-3 py-4">
                  {NAV.map((group) => (
                    <div key={group.heading} className="mb-5 last:mb-0">
                      <p className="px-3 pb-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-400 dark:text-slate-500">
                        {group.heading}
                      </p>
                      <ul className="space-y-0.5">
                        {group.items.map((item) => {
                          const isActive = item.id === active;
                          return (
                            <li key={item.id}>
                              <button
                                type="button"
                                onClick={() => selectItem(item.id)}
                                aria-current={isActive ? "page" : undefined}
                                className={`group flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900 ${
                                  isActive
                                    ? "bg-indigo-50 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300"
                                    : "text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-white"
                                }`}
                              >
                                <span
                                  className={
                                    isActive
                                      ? "text-indigo-600 dark:text-indigo-300"
                                      : "text-slate-400 group-hover:text-slate-600 dark:text-slate-500 dark:group-hover:text-slate-300"
                                  }
                                >
                                  {item.icon}
                                </span>
                                <span className="flex-1 text-left">{item.label}</span>
                                {item.badge && (
                                  <span
                                    className={`inline-flex min-w-[1.4rem] items-center justify-center rounded-full px-1.5 py-0.5 text-[11px] font-semibold ${
                                      isActive
                                        ? "bg-indigo-600 text-white dark:bg-indigo-400 dark:text-slate-900"
                                        : "bg-slate-200 text-slate-600 dark:bg-slate-700 dark:text-slate-200"
                                    }`}
                                  >
                                    {item.badge}
                                  </span>
                                )}
                              </button>
                            </li>
                          );
                        })}
                      </ul>
                    </div>
                  ))}
                </nav>

                <div className="border-t border-slate-200 p-3 dark:border-slate-800">
                  <div className="rounded-2xl bg-gradient-to-br from-indigo-500 to-violet-600 p-4 text-white">
                    <div className="flex items-center gap-2">
                      <IconBolt className="h-4 w-4" />
                      <p className="text-sm font-semibold">Free plan</p>
                    </div>
                    <p className="mt-1 text-xs text-indigo-100">3 of 5 projects used this month.</p>
                    <div className="mt-2.5 h-1.5 w-full overflow-hidden rounded-full bg-white/25">
                      <div className="h-full w-3/5 rounded-full bg-white" />
                    </div>
                    <button
                      type="button"
                      className="mt-3 inline-flex w-full items-center justify-center gap-1.5 rounded-lg bg-white/95 px-3 py-2 text-sm font-semibold text-indigo-700 transition-colors hover:bg-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-600"
                    >
                      Upgrade plan
                      <IconArrow className="h-4 w-4" />
                    </button>
                  </div>
                </div>
              </motion.aside>
            )}
          </AnimatePresence>
        </div>

        <p className="mx-auto mt-4 max-w-xl text-center text-xs text-slate-500 dark:text-slate-400">
          Use the menu button to open the sidebar. Inside it, Tab cycles the links, Enter selects, and Esc
          (or a click outside) closes it and returns focus to the button.
        </p>
      </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

Search Navbar

Search Navbar

Original

navbar with an expanding search

Gradient Navbar

Gradient Navbar

Original

gradient navbar