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 · lighticonslucide-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>
);
}