Cnippet

Multi-level Collapsible Menu

Navigation 79

About

A nested, multi-level collapsible navigation menu inside a card, with expandable folders, animated chevrons, and selectable items.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Multi-level Collapsible Menu to your project and fits it to your theme.

Code

How Multi-level Collapsible Menu is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import {
  BellIcon,
  ChartBarIcon,
  ChevronRightIcon,
  CreditCardIcon,
  FileTextIcon,
  LayoutDashboardIcon,
  MessageSquareIcon,
  SettingsIcon,
  ShieldIcon,
  UserIcon,
} from "lucide-react";
import { type ReactElement, useState } from "react";
import {
  Card,
  CardContent,
} from "@/components/ui/multi-level-collapsible-menu-utils/card";
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/multi-level-collapsible-menu-utils/collapsible";
import {
  Item,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/multi-level-collapsible-menu-utils/item";

type NavItem = {
  id: string;
  name: string;
  icon: ReactElement;
  items?: NavItem[];
};

const navItems: NavItem[] = [
  {
    icon: <LayoutDashboardIcon />,
    id: "dashboard",
    items: [
      {
        icon: <ChartBarIcon />,
        id: "analytics",
        items: [
          {
            icon: <FileTextIcon aria-hidden="true" />,
            id: "real-time",
            name: "Real-time",
          },
          {
            icon: <FileTextIcon aria-hidden="true" />,
            id: "historical",
            name: "Historical",
          },
        ],
        name: "Analytics",
      },
      {
        icon: <MessageSquareIcon aria-hidden="true" />,
        id: "reports",
        name: "Reports",
      },
    ],
    name: "Dashboard",
  },
  {
    icon: <UserIcon aria-hidden="true" />,
    id: "team",
    items: [
      { icon: <UserIcon aria-hidden="true" />, id: "members", name: "Members" },
      {
        icon: <ShieldIcon aria-hidden="true" />,
        id: "permissions",
        name: "Permissions",
      },
    ],
    name: "Team",
  },
  {
    icon: <CreditCardIcon aria-hidden="true" />,
    id: "billing",
    name: "Billing",
  },
  {
    icon: <SettingsIcon aria-hidden="true" />,
    id: "settings",
    name: "Settings",
  },
  {
    icon: <BellIcon aria-hidden="true" />,
    id: "notifications",
    name: "Notifications",
  },
];

const defaultOpenIds = new Set(["dashboard", "analytics"]);

function NavMenuItem({
  item,
  level = 0,
  selectedId,
  onSelect,
}: {
  item: NavItem;
  level?: number;
  selectedId: string | null;
  onSelect: (id: string) => void;
}) {
  const isFolder = !!item.items && item.items.length > 0;
  const isSelected = selectedId === item.id;

  if (isFolder) {
    return (
      <Collapsible
        className="group/collapsible"
        defaultOpen={defaultOpenIds.has(item.id)}
      >
        <CollapsibleTrigger
          nativeButton={false}
          render={
            <Item
              className="group/item cursor-pointer py-1.25 hover:bg-accent data-[state=open]:bg-accent"
              size="xs"
              style={{ paddingLeft: `${level * 12 + 8}px` }}
            />
          }
        >
          <ItemMedia variant="icon">
            <div className="size-3.5 text-muted-foreground group-hover/item:text-foreground">
              {item.icon}
            </div>
          </ItemMedia>
          <ItemTitle className="text-sm data-[state=open]/collapsible:font-semibold">
            {item.name}
          </ItemTitle>
          <ChevronRightIcon
            aria-hidden="true"
            className="ml-auto size-4 in-data-open:rotate-90 text-muted-foreground transition-transform"
          />
        </CollapsibleTrigger>
        <CollapsibleContent className="overflow-hidden pt-0.5 data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
          <div className="flex flex-col gap-0.5">
            {item.items?.map((child) => (
              <NavMenuItem
                item={child}
                key={child.id}
                level={level + 1}
                onSelect={onSelect}
                selectedId={selectedId}
              />
            ))}
          </div>
        </CollapsibleContent>
      </Collapsible>
    );
  }

  return (
    <Item
      className="group/item cursor-pointer py-1.25 hover:bg-accent data-[active=true]:bg-accent data-[active=true]:text-foreground"
      data-active={isSelected}
      onClick={() => onSelect(item.id)}
      size="xs"
      style={{ paddingLeft: `${level * 12 + 8}px` }}
    >
      <ItemMedia variant="icon">
        <div className="size-3.5 text-muted-foreground group-hover/item:text-foreground group-data-[active=true]/item:text-foreground">
          {item.icon}
        </div>
      </ItemMedia>
      <ItemTitle className="text-sm">{item.name}</ItemTitle>
    </Item>
  );
}

function ExpandedMenu() {
  const [selectedId, setSelectedId] = useState<string | null>("real-time");

  return (
    <div className="min-h-64 w-full max-w-56">
      <Card className="p-0">
        <CardContent className="p-1">
          <div className="flex flex-col gap-0.5">
            {navItems.map((item) => (
              <NavMenuItem
                item={item}
                key={item.id}
                onSelect={setSelectedId}
                selectedId={selectedId}
              />
            ))}
          </div>
        </CardContent>
      </Card>
    </div>
  );
}

export default function MultiLevelCollapsibleMenuDemo() {
  return (
    <div className="flex min-h-96 w-full items-center justify-center p-6">
      <ExpandedMenu />
    </div>
  );
}

Dependencies

More by Cnippet

View all
  • Team

    Team

    1.3K
  • Davincho Hero-1

    Davincho Hero-1

    682
  • Aero Hero-3

    Aero Hero-3

    558
  • Features

    Features

    508
  • Random Letter Swap Navigation

    Random Letter Swap Navigation

    484
  • Services with Animated Hover Modal

    Services with Animated Hover Modal

    463
  • Blogs

    Blogs

    396
  • Aero Hero-1

    Aero Hero-1

    356

More in Navigation

View all
  • Sidebar

    Sidebar

    231
  • Tabs

    Tabs

    60
  • Progress Bar Stepper

    Progress Bar Stepper

    36
  • Newsletter Signup Footer

    Newsletter Signup Footer

    15
  • Footer 2

    Footer 2

    200
  • Navbar with Theme Toggle

    Navbar with Theme Toggle

    43
  • Numberless Pagination with Text

    Numberless Pagination with Text

    11
  • Navbar

    Navbar

    71