shadimage
Blocks.so
blocksidebar-01source: ok · hit

Sidebar Collapsible Sections

A sidebar collapsible sections block.

npx shadcn add "https://blocks.so/r/sidebar-01.json"
free

Where it goes

sidebardashboard-shell

Interaction

accordion

Use cases

dashboard

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
iconslucide-react

Registry dependencies

avatarbuttoncollapsiblecommanddropdown-menusidebartooltip

Files7 · source cached 10/14/2026, 12:21:52 PM

content/components/sidebar/sidebar-01/nav-footer.tsx
'use client';

import {
  BookmarkPlus,
  CircleHelp,
  LogOut,
  Plus,
  PlusCircle,
  Puzzle,
  Settings,
  User,
} from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
  SidebarFooter,
  SidebarMenu,
  SidebarMenuItem,
} from '@/components/ui/sidebar';
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from '@/components/ui/tooltip';

export function NavFooter({
  user,
}: {
  user: {
    name: string;
    email: string;
    avatar: string;
  };
}) {
  return (
    <SidebarFooter className="p-4">
      <SidebarMenu>
        <SidebarMenuItem>
          <div className="flex items-center justify-between gap-2">
            <div className="flex items-center gap-2">
              <DropdownMenu>
                <DropdownMenuTrigger
                  nativeButton={false}
                  render={<Avatar className="h-8 w-8 rounded-full" />}
                >
                  <AvatarImage alt={user.name} src={user.avatar} />
                  <AvatarFallback className="rounded-full">CN</AvatarFallback>
                </DropdownMenuTrigger>
                <DropdownMenuContent className="m-2">
                  <DropdownMenuItem>
                    <User aria-hidden="true" className="opacity-80" size={16} />
                    Profile
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <Settings
                      aria-hidden="true"
                      className="opacity-80"
                      size={16}
                    />
                    Settings
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <LogOut
                      aria-hidden="true"
                      className="opacity-80"
                      size={16}
                    />
                    Logout
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>

              <TooltipProvider delay={0}>
                <Tooltip>
                  <TooltipTrigger
                    render={
                      <CircleHelp
                        aria-hidden="true"
                        className="cursor-pointer opacity-60 hover:opacity-100"
                        size={16}
                      />
                    }
                  />
                  <TooltipContent
                    className="m-2 max-w-[150px] border bg-popover px-2 py-1 text-popover-foreground"
                    side="top"
                  >
                    <div className="space-y-1 text-xs">
                      <p className="font-medium">User Information</p>
                      <p className="text-muted-foreground">
                        More details about the current user or section can be
                        displayed here.
                      </p>
                    </div>
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
            </div>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={
                  <Button
                    aria-label="Open edit menu"
                    className="rounded-full shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
                    size="icon"
                    variant="ghost"
                  />
                }
              >
                <Plus aria-hidden="true" size={16} />
              </DropdownMenuTrigger>
              <DropdownMenuContent className="pb-2">
                <DropdownMenuLabel>Add New</DropdownMenuLabel>
                <DropdownMenuItem>
                  <PlusCircle
                    aria-hidden="true"
                    className="mr-2 opacity-80"
                    size={16}
                  />
                  Add New Item
                </DropdownMenuItem>
                <DropdownMenuItem>
                  <BookmarkPlus
                    aria-hidden="true"
                    className="mr-2 opacity-80"
                    size={16}
                  />
                  Add Bookmark
                </DropdownMenuItem>
                <DropdownMenuItem>
                  <Puzzle
                    aria-hidden="true"
                    className="mr-2 opacity-80"
                    size={16}
                  />
                  Add Integration
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarFooter>
  );
}
content/components/sidebar/sidebar-01/types.ts
import type { ElementType } from 'react';

export interface NavItem {
  id: string;
  title: string;
  icon: ElementType;
  url?: string;
  isActive?: boolean;
}

export interface User {
  name: string;
  email: string;
  avatar: string;
}

export interface FavoriteItem {
  id: string;
  title: string;
  href: string;
  color: string;
}

export interface TeamItem {
  id: string;
  title: string;
  icon: ElementType;
}

export interface TopicItem {
  id: string;
  title: string;
  icon: ElementType;
}

export interface SidebarData {
  user: User;
  navMain: NavItem[];
  navCollapsible: {
    favorites: FavoriteItem[];
    teams: TeamItem[];
    topics: TopicItem[];
  };
}
content/components/sidebar/sidebar-01/nav-main.tsx
'use client';

import {
  SidebarGroup,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/components/ui/sidebar';
import type { NavItem } from '@/components/sidebar-01/types';

export function NavMain({ items }: { items: NavItem[] }) {
  return (
    <SidebarGroup>
      <SidebarMenu>
        {items.map((item) => {
          const Icon = item.icon;

          return (
            <SidebarMenuItem key={item.id}>
              <SidebarMenuButton tooltip={item.title}>
                {Icon && <Icon className="mr-2 h-4 w-4" />}
                <span>{item.title}</span>
              </SidebarMenuButton>
            </SidebarMenuItem>
          );
        })}
      </SidebarMenu>
    </SidebarGroup>
  );
}
content/components/sidebar/sidebar-01/nav-collapsible.tsx
'use client';
import { ChevronDown } from 'lucide-react';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/components/ui/sidebar';
import type { FavoriteItem, TeamItem, TopicItem } from '@/components/sidebar-01/types';

interface NavCollapsibleProps {
  favorites: FavoriteItem[];
  teams: TeamItem[];
  topics: TopicItem[];
}

export function NavCollapsible({
  favorites,
  teams,
  topics,
}: NavCollapsibleProps) {
  return (
    <div className="space-y-0">
      {favorites && favorites.length > 0 && (
        <Collapsible className="group/collapsible" defaultOpen>
          <SidebarGroup>
            <SidebarGroupLabel
              className="text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
              render={<CollapsibleTrigger />}
            >
              Favorites
              <ChevronDown className="ml-auto transition-transform group-data-open/collapsible:rotate-180" />
            </SidebarGroupLabel>
            <CollapsibleContent>
              <SidebarGroupContent>
                <SidebarMenu>
                  {favorites.map((item) => (
                    <SidebarMenuItem key={item.id}>
                      <SidebarMenuButton
                        render={
                          <a
                            className="flex items-center gap-3"
                            href={item.href}
                          />
                        }
                      >
                        <div className={`h-3 w-3 rounded ${item.color}`} />
                        <span>{item.title}</span>
                      </SidebarMenuButton>
                    </SidebarMenuItem>
                  ))}
                </SidebarMenu>
              </SidebarGroupContent>
            </CollapsibleContent>
          </SidebarGroup>
        </Collapsible>
      )}

      {[
        { label: 'Teams', items: teams },
        { label: 'Topics', items: topics },
      ].map(
        ({ label, items }) =>
          items &&
          items.length > 0 && (
            <Collapsible className="group/collapsible" key={label}>
              <SidebarGroup>
                <SidebarGroupLabel
                  className="text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
                  render={<CollapsibleTrigger />}
                >
                  {label}
                  <ChevronDown className="ml-auto transition-transform group-data-open/collapsible:rotate-180" />
                </SidebarGroupLabel>
                <CollapsibleContent>
                  <SidebarGroupContent>
                    <SidebarMenu>
                      {items.map((item) => {
                        const Icon = item.icon;
                        return (
                          <SidebarMenuItem key={item.id}>
                            <SidebarMenuButton>
                              <Icon className="mr-2 h-4 w-4" />
                              {item.title}
                            </SidebarMenuButton>
                          </SidebarMenuItem>
                        );
                      })}
                    </SidebarMenu>
                  </SidebarGroupContent>
                </CollapsibleContent>
              </SidebarGroup>
            </Collapsible>
          )
      )}
    </div>
  );
}
content/components/sidebar/sidebar-01/app-sidebar.tsx
'use client';

import {
  IconAd2,
  IconBellRinging,
  IconCalendar,
  IconCalendarStats,
  IconListDetails,
  IconNews,
  IconNotebook,
  IconProgressCheck,
  IconSettingsCode,
} from '@tabler/icons-react';
import { LayoutDashboard, Package } from 'lucide-react';
import { Sidebar, SidebarContent } from '@/components/ui/sidebar';
import { NavCollapsible } from '@/components/sidebar-01/nav-collapsible';
import { NavFooter } from '@/components/sidebar-01/nav-footer';
import { NavHeader } from '@/components/sidebar-01/nav-header';
import { NavMain } from '@/components/sidebar-01/nav-main';
import type { SidebarData } from '@/components/sidebar-01/types';

const data: SidebarData = {
  user: {
    name: 'ephraim',
    email: 'ephraim@blocks.so',
    avatar: '/avatar-01.png',
  },
  navMain: [
    {
      id: 'overview',
      title: 'Overview',
      url: '#',
      icon: LayoutDashboard,
      isActive: true,
    },
    {
      id: 'tasks',
      title: 'Tasks',
      url: '#',
      icon: IconListDetails,
    },
    {
      id: 'meetings',
      title: 'Meetings',
      url: '#',
      icon: IconCalendarStats,
    },
    {
      id: 'notes',
      title: 'Notes',
      url: '#',
      icon: IconNotebook,
    },
    {
      id: 'calendar',
      title: 'Calendar',
      url: '#',
      icon: IconCalendar,
    },
    {
      id: 'completed',
      title: 'Completed',
      url: '#',
      icon: IconProgressCheck,
    },
    {
      id: 'notifications',
      title: 'Notifications',
      url: '#',
      icon: IconBellRinging,
    },
  ],
  navCollapsible: {
    favorites: [
      {
        id: 'design',
        title: 'Design',
        href: '#',
        color: 'bg-green-400 dark:bg-green-300',
      },
      {
        id: 'development',
        title: 'Development',
        href: '#',
        color: 'bg-blue-400 dark:bg-blue-300',
      },
      {
        id: 'workshop',
        title: 'Workshop',
        href: '#',
        color: 'bg-orange-400 dark:bg-orange-300',
      },
      {
        id: 'personal',
        title: 'Personal',
        href: '#',
        color: 'bg-red-400 dark:bg-red-300',
      },
    ],
    teams: [
      {
        id: 'engineering',
        title: 'Engineering',
        icon: IconSettingsCode,
      },
      {
        id: 'marketing',
        title: 'Marketing',
        icon: IconAd2,
      },
    ],
    topics: [
      {
        id: 'product-updates',
        title: 'Product Updates',
        icon: Package,
      },
      {
        id: 'company-news',
        title: 'Company News',
        icon: IconNews,
      },
    ],
  },
};

export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
  return (
    <Sidebar {...props}>
      <NavHeader data={data} />
      <SidebarContent>
        <NavMain items={data.navMain} />
        <NavCollapsible
          favorites={data.navCollapsible.favorites}
          teams={data.navCollapsible.teams}
          topics={data.navCollapsible.topics}
        />
      </SidebarContent>
      <NavFooter user={data.user} />
    </Sidebar>
  );
}
content/components/sidebar/sidebar-01/nav-header.tsx
'use client';

import { Search } from 'lucide-react';
import * as React from 'react';
import { useEffect } from 'react';
import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
} from '@/components/ui/command';
import { SidebarHeader } from '@/components/ui/sidebar';
import { cn } from '@/lib/utils';
import type { SidebarData } from '@/components/sidebar-01/types';

interface NavHeaderProps {
  data: SidebarData;
}

export function NavHeader({ data }: NavHeaderProps) {
  const [open, setOpen] = React.useState(false);

  useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        setOpen((open) => !open);
      }
    };
    document.addEventListener('keydown', down);
    return () => document.removeEventListener('keydown', down);
  }, []);

  return (
    <>
      <SidebarHeader>
        <div
          className="flex cursor-pointer items-center justify-between px-2 pt-3 pb-0"
          onClick={() => setOpen(true)}
        >
          <div className="flex flex-1 items-center gap-3">
            <Search className="h-4 w-4 text-muted-foreground" />
            <span className="font-normal text-muted-foreground text-sm">
              Search
            </span>
          </div>
          <div className="flex items-center justify-center rounded-md border border-border px-2 py-1">
            <kbd className="inline-flex font-[inherit] font-medium text-muted-foreground text-xs">
              <span className="opacity-70">⌘K</span>
            </kbd>
          </div>
        </div>
      </SidebarHeader>

      <CommandDialog onOpenChange={setOpen} open={open}>
        <Command>
          <CommandInput placeholder="Search everything..." />
          <CommandList>
            <CommandEmpty>No results found.</CommandEmpty>
            <CommandGroup heading="Navigation">
              {data.navMain.map((item) => (
                <CommandItem
                  className="py-2!"
                  key={item.id}
                  onSelect={() => {
                    setOpen(false);
                  }}
                >
                  <item.icon className="mr-2 h-4 w-4" />
                  <span>{item.title}</span>
                </CommandItem>
              ))}
            </CommandGroup>
            <CommandSeparator className="my-2" />
            <CommandGroup heading="Favorites">
              {data.navCollapsible.favorites.map((item) => (
                <CommandItem
                  className="py-2!"
                  key={item.id}
                  onSelect={() => {
                    setOpen(false);
                  }}
                >
                  <div
                    className={cn('mr-2 h-3 w-3 rounded-full', item.color)}
                  />
                  <span>{item.title}</span>
                </CommandItem>
              ))}
            </CommandGroup>
            <CommandSeparator className="my-2" />
            <CommandGroup heading="Teams">
              {data.navCollapsible.teams.map((item) => (
                <CommandItem
                  className="py-2!"
                  key={item.id}
                  onSelect={() => {
                    setOpen(false);
                  }}
                >
                  <item.icon className="mr-2 h-4 w-4" />
                  <span>{item.title}</span>
                </CommandItem>
              ))}
            </CommandGroup>
            <CommandSeparator className="my-2" />
            <CommandGroup heading="Topics">
              {data.navCollapsible.topics.map((item) => (
                <CommandItem
                  className="py-2!"
                  key={item.id}
                  onSelect={() => {
                    setOpen(false);
                  }}
                >
                  <item.icon className="mr-2 h-4 w-4" />
                  <span>{item.title}</span>
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </CommandDialog>
    </>
  );
}
content/components/sidebar/sidebar-01/index.tsx
import {
  SidebarInset,
  SidebarProvider,
  SidebarTrigger,
} from '@/components/ui/sidebar';
import { AppSidebar } from '@/components/sidebar-01/app-sidebar';

export default function Sidebar01() {
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset>
        <header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
          <div className="flex items-center gap-2 px-4">
            <SidebarTrigger className="sm:hidden" />
          </div>
        </header>
      </SidebarInset>
    </SidebarProvider>
  );
}

More from Blocks.so

Browse all components in this library →