A collapsible sidebar navigation inspired by Linear. Includes grouped nav items, active states, count badges, and smooth collapse animation.
WORKSPACE
MY TEAMS
FAVORITES
Select an item from the sidebar
// Dependencies: react ^18, lucide-react
import React, { useState } from 'react';
import {
Inbox, FileText, Eye, FolderOpen, AlertCircle, RotateCcw,
Map, Archive, Star, Bug, Layers, Settings, UserPlus,
ChevronRight, ChevronLeft, ChevronDown,
} from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
function cn(...classes: (string | false | null | undefined)[]) {
return classes.filter(Boolean).join(' ');
}
type NavItem = {
id: string;
label: string;
icon: LucideIcon;
count?: number;
section: string;
};
const NAV_ITEMS: NavItem[] = [
{ id: 'inbox', label: 'Inbox', icon: Inbox, count: 3, section: 'WORKSPACE' },
{ id: 'my-issues', label: 'My Issues', icon: FileText, section: 'WORKSPACE' },
{ id: 'views', label: 'Views', icon: Eye, section: 'WORKSPACE' },
{ id: 'projects', label: 'Projects', icon: FolderOpen, section: 'WORKSPACE' },
{ id: 'issues', label: 'Issues', icon: AlertCircle, section: 'MY TEAMS' },
{ id: 'cycles', label: 'Cycles', icon: RotateCcw, section: 'MY TEAMS' },
{ id: 'roadmap', label: 'Roadmap', icon: Map, section: 'MY TEAMS' },
{ id: 'backlog', label: 'Backlog', icon: Archive, section: 'MY TEAMS' },
{ id: 'q3-planning', label: 'Q3 Planning', icon: Star, section: 'FAVORITES' },
{ id: 'bug-fixes', label: 'Bug Fixes', icon: Bug, section: 'FAVORITES' },
{ id: 'design-system', label: 'Design System', icon: Layers, section: 'FAVORITES' },
];
const BOTTOM_ITEMS: NavItem[] = [
{ id: 'settings', label: 'Settings', icon: Settings, section: 'BOTTOM' },
{ id: 'invite', label: 'Invite Members', icon: UserPlus, section: 'BOTTOM' },
];
type SidebarProps = {
activeId: string;
onSelect: (id: string) => void;
collapsed: boolean;
onToggleCollapse: () => void;
};
export function SidebarNavigation({ activeId, onSelect, collapsed, onToggleCollapse }: SidebarProps) {
const sections = ['WORKSPACE', 'MY TEAMS', 'FAVORITES'];
return (
<div className={cn(
'flex flex-col h-full bg-[#0f0f0f] border-r border-white/[0.06] transition-all duration-300 ease-in-out shrink-0',
collapsed ? 'w-[52px]' : 'w-[240px]'
)}>
<div className={cn(
'flex items-center h-12 border-b border-white/[0.06] px-3',
collapsed ? 'justify-center' : 'justify-between'
)}>
<div className={cn('flex items-center gap-2 min-w-0', collapsed && 'justify-center')}>
<div className="w-5 h-5 rounded bg-indigo-500 flex items-center justify-center shrink-0">
<span className="text-[10px] font-bold text-white">A</span>
</div>
{!collapsed && <span className="text-sm font-semibold text-white truncate">Acme Corp</span>}
</div>
{!collapsed && <ChevronDown className="w-3.5 h-3.5 text-zinc-600 shrink-0" />}
</div>
<div className="flex-1 overflow-y-auto py-2">
{sections.map((section) => {
const items = NAV_ITEMS.filter((item) => item.section === section);
return (
<div key={section} className="mb-3">
{!collapsed && (
<p className="px-3 py-1 text-[10px] font-semibold tracking-widest text-zinc-600 uppercase">
{section}
</p>
)}
{items.map((item) => (
<NavItemButton
key={item.id}
item={item}
isActive={activeId === item.id}
collapsed={collapsed}
onClick={() => onSelect(item.id)}
/>
))}
</div>
);
})}
</div>
<div className="border-t border-white/[0.06] py-2">
{BOTTOM_ITEMS.map((item) => (
<NavItemButton
key={item.id}
item={item}
isActive={activeId === item.id}
collapsed={collapsed}
onClick={() => onSelect(item.id)}
/>
))}
<button
onClick={onToggleCollapse}
className={cn(
'flex items-center gap-2.5 px-3 py-2 mx-1 rounded-md text-zinc-600 hover:text-zinc-300 hover:bg-white/[0.04] transition-colors',
collapsed ? 'justify-center' : ''
)}
style={{ width: 'calc(100% - 8px)' }}
title={collapsed ? 'Expand' : 'Collapse'}
>
{collapsed ? (
<ChevronRight className="w-3.5 h-3.5 shrink-0" />
) : (
<><ChevronLeft className="w-3.5 h-3.5 shrink-0" /><span className="text-xs">Collapse</span></>
)}
</button>
</div>
</div>
);
}
function NavItemButton({ item, isActive, collapsed, onClick }: {
item: NavItem; isActive: boolean; collapsed: boolean; onClick: () => void;
}) {
const Icon = item.icon;
return (
<button
onClick={onClick}
title={collapsed ? item.label : undefined}
className={cn(
'flex items-center gap-2.5 px-3 py-1.5 mx-1 rounded-md text-left transition-colors',
collapsed ? 'justify-center' : '',
isActive ? 'bg-white/[0.08] text-white' : 'text-zinc-500 hover:text-zinc-300 hover:bg-white/[0.04]'
)}
style={{ width: 'calc(100% - 8px)' }}
>
<Icon className="w-3.5 h-3.5 shrink-0" />
{!collapsed && (
<>
<span className="flex-1 text-xs font-medium">{item.label}</span>
{item.count !== undefined && (
<span className="text-[10px] font-semibold text-zinc-400 bg-zinc-800 rounded px-1.5 py-0.5 shrink-0">
{item.count}
</span>
)}
</>
)}
</button>
);
}
// Usage demo
export function SidebarNavigationDemo() {
const [activeId, setActiveId] = useState('inbox');
const [collapsed, setCollapsed] = useState(false);
return (
<div className="flex h-[500px] bg-[#0a0a0a] rounded-xl border border-white/[0.06] overflow-hidden">
<SidebarNavigation
activeId={activeId}
onSelect={setActiveId}
collapsed={collapsed}
onToggleCollapse={() => setCollapsed((v) => !v)}
/>
<div className="flex-1 flex items-center justify-center">
<p className="text-sm text-zinc-600">Select an item from the sidebar</p>
</div>
</div>
);
}Unlock to copy
Free access to all patterns
Command Palette
Linear
Global Search with Results Grouping
Linear
Recent + Suggested Search
Raycast
Filter Builder (AND / OR)
Linear
Sidebar Navigation
Linear
Collapsible Nested Tree Nav
Notion
Tab Bar with Overflow Menu
Vercel
Breadcrumb with Dropdowns
Linear
Workspace Switcher
Slack
Deployment Status Card
Vercel
Empty State
Vercel
Error State with Retry
Notion
Slash Command Menu
Notion
Stats & Metrics Row
Vercel
Single Big Metric Card
Stripe
Usage / Quota Meter
Vercel
Skeleton Loading Grid
Linear
Activity Heatmap
Notion
Sparkline Trend Cards
Linear
Confirmation Dialog
Linear
Slide-Over Panel
Stripe
Bottom Sheet
Stripe
Toast / Snackbar Stack
Vercel
Notification Center
Linear
Issue / Task Card
Linear
Subtask Checklist
Linear
Kanban Board Column
Linear
Activity Feed
Linear + Slack
Comment Thread
Linear
Audit Log Timeline
Vercel
Properties Panel
Notion + Linear
Multi-Step Form Wizard
Stripe
Tag Multi-Select
Linear
Toggle Switch Group
Stripe
File Upload Dropzone
Vercel
Inline Data Table
Notion + Linear
Pagination
Vercel
Sortable / Selectable Table
Linear
Row Detail Drawer
Stripe
Editable Cell
Notion
Status Badge System
Linear
Segmented Control
Notion