UIPackage

Sidebar 02

block sidebar
Edit on GitHub

Full-featured app sidebar -- collapsible="icon" rail with TeamSwitcher header, primary nav, projects section, secondary nav, and user dropdown footer. Each section ships as a sibling file in the same folder so consumers can edit one piece at a time.

Also available for Vue ->

Installation

$ npx shadcn@latest add https://react.uipkge.dev/r/react/sidebar-02.json

Or with the named registry: npx shadcn@latest add @uipkge-react/sidebar-02

Examples

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

Team
type Team {
  name: string
  logo: LucideIcon | ((props: { className?: string }) => React.ReactElement)
  plan: string
}

Files (6)

  • components/blocks/sidebar-02/Sidebar02.tsx 2.3 kB
    'use client'
    
    import { BookOpen, Bot, Frame, LifeBuoy, Map, PieChart, Send, Settings2, SquareTerminal } from 'lucide-react'
    import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from '@/components/ui/sidebar'
    import { NavMain } from './NavMain'
    import { NavProjects } from './NavProjects'
    import { NavSecondary } from './NavSecondary'
    import { NavUser } from './NavUser'
    import { TeamSwitcher } from './TeamSwitcher'
    
    const data = {
      user: {
        name: 'shadcn',
        email: '[email protected]',
      },
      navMain: [
        {
          title: 'Playground',
          url: '#',
          icon: SquareTerminal,
          isActive: true,
          items: [
            { title: 'History', url: '#' },
            { title: 'Starred', url: '#' },
            { title: 'Settings', url: '#' },
          ],
        },
        {
          title: 'Models',
          url: '#',
          icon: Bot,
          items: [
            { title: 'Genesis', url: '#' },
            { title: 'Explorer', url: '#' },
            { title: 'Quantum', url: '#' },
          ],
        },
        {
          title: 'Documentation',
          url: '#',
          icon: BookOpen,
          items: [
            { title: 'Introduction', url: '#' },
            { title: 'Get Started', url: '#' },
            { title: 'Tutorials', url: '#' },
            { title: 'Changelog', url: '#' },
          ],
        },
        {
          title: 'Settings',
          url: '#',
          icon: Settings2,
          items: [
            { title: 'General', url: '#' },
            { title: 'Team', url: '#' },
            { title: 'Billing', url: '#' },
            { title: 'Limits', url: '#' },
          ],
        },
      ],
      navSecondary: [
        { title: 'Support', url: '#', icon: LifeBuoy },
        { title: 'Feedback', url: '#', icon: Send },
      ],
      projects: [
        { name: 'Design Engineering', url: '#', icon: Frame },
        { name: 'Sales & Marketing', url: '#', icon: PieChart },
        { name: 'Travel', url: '#', icon: Map },
      ],
    }
    
    export function Sidebar02() {
      return (
        <Sidebar collapsible="icon">
          <SidebarHeader>
            <TeamSwitcher />
          </SidebarHeader>
          <SidebarContent>
            <NavMain items={data.navMain} />
            <NavProjects projects={data.projects} />
            <NavSecondary items={data.navSecondary} className="mt-auto" />
          </SidebarContent>
          <SidebarFooter>
            <NavUser user={data.user} />
          </SidebarFooter>
          <SidebarRail />
        </Sidebar>
      )
    }
  • components/blocks/sidebar-02/TeamSwitcher.tsx 4.3 kB
    'use client'
    
    // Edit teams + activeTeam below to match your tenant model. The dropdown
    // is the full team switcher pattern -- avatar tile, label, kbd shortcut,
    // and a "Add team" footer row. Wire setActive() to your tenant API.
    
    import * as React from 'react'
    import { AudioWaveform, Check, ChevronsUpDown, Command, Plus, type LucideIcon } from 'lucide-react'
    import {
      DropdownMenu,
      DropdownMenuContent,
      DropdownMenuItem,
      DropdownMenuLabel,
      DropdownMenuSeparator,
      DropdownMenuShortcut,
      DropdownMenuTrigger,
    } from '@/components/ui/dropdown-menu'
    import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar'
    
    // 4-quadrant uipkge logo. Inline component so it can sit next to Lucide
    // icons in the teams array without an extra file.
    const UipkgeMark = ({ className }: { className?: string }) => (
      <svg viewBox="0 0 24 24" className={className} fill="currentColor" aria-hidden="true">
        <rect x="2" y="2" width="9" height="9" rx="1.5" />
        <rect x="13" y="2" width="9" height="9" rx="1.5" opacity="0.55" />
        <rect x="2" y="13" width="9" height="9" rx="1.5" opacity="0.55" />
        <rect x="13" y="13" width="9" height="9" rx="1.5" />
      </svg>
    )
    
    type Team = {
      name: string
      logo: LucideIcon | ((props: { className?: string }) => React.ReactElement)
      plan: string
    }
    
    const teams: Team[] = [
      { name: 'uipkge', logo: UipkgeMark, plan: 'UI registry' },
      { name: 'uipkge HRMS', logo: AudioWaveform, plan: 'Vertical' },
      { name: 'uipkge Hospital', logo: Command, plan: 'Vertical' },
    ]
    
    export function TeamSwitcher() {
      const { isMobile } = useSidebar()
      const [activeTeam, setActiveTeam] = React.useState<Team>(teams[0]!)
      const ActiveLogo = activeTeam.logo
    
      return (
        <SidebarMenu>
          <SidebarMenuItem>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <SidebarMenuButton
                  size="lg"
                  className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center"
                >
                  <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg group-data-[collapsible=icon]:size-6">
                    <ActiveLogo className="size-4 group-data-[collapsible=icon]:size-3.5" />
                  </div>
                  <div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
                    <span className="font-display truncate font-bold">{activeTeam.name}</span>
                    <span className="text-muted-foreground truncate text-xs">{activeTeam.plan}</span>
                  </div>
                  <ChevronsUpDown className="ml-auto size-4 group-data-[collapsible=icon]:hidden" />
                </SidebarMenuButton>
              </DropdownMenuTrigger>
              <DropdownMenuContent
                className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
                side={isMobile ? 'bottom' : 'right'}
                align="start"
                sideOffset={4}
              >
                <DropdownMenuLabel className="text-muted-foreground text-xs">Teams</DropdownMenuLabel>
                {teams.map((team, i) => {
                  const Logo = team.logo
                  return (
                    <DropdownMenuItem key={team.name} className="gap-2 p-2" onSelect={() => setActiveTeam(team)}>
                      <div className="flex size-6 items-center justify-center rounded-sm border">
                        <Logo className="size-3.5 shrink-0" />
                      </div>
                      {team.name}
                      {activeTeam === team ? (
                        <Check className="ml-auto size-4" />
                      ) : (
                        <DropdownMenuShortcut>{i + 1}</DropdownMenuShortcut>
                      )}
                    </DropdownMenuItem>
                  )
                })}
                <DropdownMenuSeparator />
                <DropdownMenuItem className="gap-2 p-2">
                  <div className="bg-background flex size-6 items-center justify-center rounded-md border">
                    <Plus className="size-4" />
                  </div>
                  <div className="text-muted-foreground font-medium">Add team</div>
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </SidebarMenuItem>
        </SidebarMenu>
      )
    }
  • components/blocks/sidebar-02/NavMain.tsx 2.3 kB
    'use client'
    
    import { ChevronRight, type LucideIcon } from 'lucide-react'
    import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
    import {
      SidebarGroup,
      SidebarGroupLabel,
      SidebarMenu,
      SidebarMenuAction,
      SidebarMenuButton,
      SidebarMenuItem,
      SidebarMenuSub,
      SidebarMenuSubButton,
      SidebarMenuSubItem,
    } from '@/components/ui/sidebar'
    
    export interface NavMainProps {
      items: {
        title: string
        url: string
        icon: LucideIcon
        isActive?: boolean
        items?: {
          title: string
          url: string
          isActive?: boolean
        }[]
      }[]
    }
    
    export function NavMain({ items }: NavMainProps) {
      return (
        <SidebarGroup>
          <SidebarGroupLabel>Platform</SidebarGroupLabel>
          <SidebarMenu>
            {items.map((item) => (
              <Collapsible key={item.title} asChild defaultOpen={item.isActive}>
                <SidebarMenuItem>
                  <SidebarMenuButton asChild tooltip={item.title} isActive={item.isActive}>
                    <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  </SidebarMenuButton>
                  {item.items?.length ? (
                    <>
                      <CollapsibleTrigger asChild>
                        <SidebarMenuAction className="data-[state=open]:rotate-90">
                          <ChevronRight />
                          <span className="sr-only">Toggle</span>
                        </SidebarMenuAction>
                      </CollapsibleTrigger>
                      <CollapsibleContent>
                        <SidebarMenuSub>
                          {item.items.map((subItem) => (
                            <SidebarMenuSubItem key={subItem.title}>
                              <SidebarMenuSubButton asChild isActive={subItem.isActive}>
                                <a href={subItem.url}>
                                  <span>{subItem.title}</span>
                                </a>
                              </SidebarMenuSubButton>
                            </SidebarMenuSubItem>
                          ))}
                        </SidebarMenuSub>
                      </CollapsibleContent>
                    </>
                  ) : null}
                </SidebarMenuItem>
              </Collapsible>
            ))}
          </SidebarMenu>
        </SidebarGroup>
      )
    }
  • components/blocks/sidebar-02/NavProjects.tsx 2.7 kB
    'use client'
    
    import { Folder, Forward, MoreHorizontal, Trash2, type LucideIcon } from 'lucide-react'
    import {
      DropdownMenu,
      DropdownMenuContent,
      DropdownMenuItem,
      DropdownMenuSeparator,
      DropdownMenuTrigger,
    } from '@/components/ui/dropdown-menu'
    import {
      SidebarGroup,
      SidebarGroupLabel,
      SidebarMenu,
      SidebarMenuAction,
      SidebarMenuButton,
      SidebarMenuItem,
      useSidebar,
    } from '@/components/ui/sidebar'
    
    export interface NavProjectsProps {
      projects: {
        name: string
        url: string
        icon: LucideIcon
      }[]
    }
    
    export function NavProjects({ projects }: NavProjectsProps) {
      const { isMobile } = useSidebar()
    
      return (
        // Upstream shadcn-ui hides this entire group on icon-mode collapse
        // with `group-data-[collapsible=icon]:hidden`. We keep it visible so
        // the project icons stay reachable in the narrow column; the
        // SidebarGroupLabel below and the `<span>` children of each
        // SidebarMenuButton already self-hide on collapse, leaving an
        // icon-only column that lines up with NavMain.
        <SidebarGroup>
          <SidebarGroupLabel>Projects</SidebarGroupLabel>
          <SidebarMenu>
            {projects.map((item) => (
              <SidebarMenuItem key={item.name}>
                <SidebarMenuButton asChild>
                  <a href={item.url}>
                    <item.icon />
                    <span>{item.name}</span>
                  </a>
                </SidebarMenuButton>
                <DropdownMenu>
                  <DropdownMenuTrigger asChild>
                    <SidebarMenuAction showOnHover>
                      <MoreHorizontal />
                      <span className="sr-only">More</span>
                    </SidebarMenuAction>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent
                    className="w-48 rounded-lg"
                    side={isMobile ? 'bottom' : 'right'}
                    align={isMobile ? 'end' : 'start'}
                  >
                    <DropdownMenuItem>
                      <Folder className="text-muted-foreground" />
                      <span>View Project</span>
                    </DropdownMenuItem>
                    <DropdownMenuItem>
                      <Forward className="text-muted-foreground" />
                      <span>Share Project</span>
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <DropdownMenuItem>
                      <Trash2 className="text-muted-foreground" />
                      <span>Delete Project</span>
                    </DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
              </SidebarMenuItem>
            ))}
            <SidebarMenuItem>
              <SidebarMenuButton>
                <MoreHorizontal />
                <span>More</span>
              </SidebarMenuButton>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarGroup>
      )
    }
  • components/blocks/sidebar-02/NavSecondary.tsx 1 kB
    'use client'
    
    import * as React from 'react'
    import { type LucideIcon } from 'lucide-react'
    import {
      SidebarGroup,
      SidebarGroupContent,
      SidebarMenu,
      SidebarMenuButton,
      SidebarMenuItem,
    } from '@/components/ui/sidebar'
    
    export interface NavSecondaryProps extends React.ComponentProps<typeof SidebarGroup> {
      items: {
        title: string
        url: string
        icon: LucideIcon
        isActive?: boolean
      }[]
    }
    
    export function NavSecondary({ items, ...props }: NavSecondaryProps) {
      return (
        <SidebarGroup {...props}>
          <SidebarGroupContent>
            <SidebarMenu>
              {items.map((item) => (
                <SidebarMenuItem key={item.title}>
                  <SidebarMenuButton asChild size="sm" isActive={item.isActive}>
                    <a href={item.url}>
                      <item.icon />
                      <span>{item.title}</span>
                    </a>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      )
    }
  • components/blocks/sidebar-02/NavUser.tsx 4.8 kB
    'use client'
    
    import * as React from 'react'
    import {
      BadgeCheck,
      Bell,
      ChevronsUpDown,
      CreditCard,
      LogOut,
      Monitor,
      Moon,
      Palette,
      Sparkles,
      Sun,
    } from 'lucide-react'
    import { useTheme } from '@/lib/use-theme'
    import { Avatar, AvatarFallback } from '@/components/ui/avatar'
    import {
      DropdownMenu,
      DropdownMenuContent,
      DropdownMenuGroup,
      DropdownMenuItem,
      DropdownMenuLabel,
      DropdownMenuRadioGroup,
      DropdownMenuRadioItem,
      DropdownMenuSeparator,
      DropdownMenuSub,
      DropdownMenuSubContent,
      DropdownMenuSubTrigger,
      DropdownMenuTrigger,
    } from '@/components/ui/dropdown-menu'
    import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar'
    
    export interface NavUserProps {
      user: {
        name: string
        email: string
      }
    }
    
    export function NavUser({ user }: NavUserProps) {
      const { isMobile } = useSidebar()
      const { theme, setTheme } = useTheme()
    
      const initials = React.useMemo(() => {
        const parts = user.name.trim().split(/\s+/).slice(0, 2)
        return parts.map((p) => p[0]?.toUpperCase()).join('') || 'U'
      }, [user.name])
    
      return (
        <SidebarMenu>
          <SidebarMenuItem>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <SidebarMenuButton
                  size="lg"
                  className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center"
                >
                  <Avatar className="h-8 w-8 shrink-0 rounded-lg group-data-[collapsible=icon]:size-6">
                    <AvatarFallback className="rounded-lg text-xs group-data-[collapsible=icon]:text-[10px]">
                      {initials}
                    </AvatarFallback>
                  </Avatar>
                  <div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
                    <span className="truncate font-medium">{user.name}</span>
                    <span className="truncate text-xs">{user.email}</span>
                  </div>
                  <ChevronsUpDown className="ml-auto size-4 group-data-[collapsible=icon]:hidden" />
                </SidebarMenuButton>
              </DropdownMenuTrigger>
              <DropdownMenuContent
                className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
                side={isMobile ? 'bottom' : 'right'}
                align="end"
                sideOffset={4}
              >
                <DropdownMenuLabel className="p-0 font-normal">
                  <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
                    <Avatar className="h-8 w-8 rounded-lg">
                      <AvatarFallback className="rounded-lg text-xs">{initials}</AvatarFallback>
                    </Avatar>
                    <div className="grid flex-1 text-left text-sm leading-tight">
                      <span className="truncate font-semibold">{user.name}</span>
                      <span className="truncate text-xs">{user.email}</span>
                    </div>
                  </div>
                </DropdownMenuLabel>
                <DropdownMenuSeparator />
                <DropdownMenuGroup>
                  <DropdownMenuItem>
                    <Sparkles />
                    Upgrade to Pro
                  </DropdownMenuItem>
                </DropdownMenuGroup>
                <DropdownMenuSeparator />
                <DropdownMenuGroup>
                  <DropdownMenuItem>
                    <BadgeCheck />
                    Account
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <CreditCard />
                    Billing
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <Bell />
                    Notifications
                  </DropdownMenuItem>
                </DropdownMenuGroup>
                <DropdownMenuSeparator />
                <DropdownMenuSub>
                  <DropdownMenuSubTrigger>
                    <Palette />
                    Theme
                    <span className="text-muted-foreground ml-auto text-xs capitalize">{theme}</span>
                  </DropdownMenuSubTrigger>
                  <DropdownMenuSubContent className="min-w-36">
                    <DropdownMenuRadioGroup value={theme} onValueChange={(v) => setTheme(v)}>
                      <DropdownMenuRadioItem value="light">
                        <Sun /> Light
                      </DropdownMenuRadioItem>
                      <DropdownMenuRadioItem value="dark">
                        <Moon /> Dark
                      </DropdownMenuRadioItem>
                      <DropdownMenuRadioItem value="system">
                        <Monitor /> System
                      </DropdownMenuRadioItem>
                    </DropdownMenuRadioGroup>
                  </DropdownMenuSubContent>
                </DropdownMenuSub>
                <DropdownMenuSeparator />
                <DropdownMenuItem>
                  <LogOut />
                  Log out
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </SidebarMenuItem>
        </SidebarMenu>
      )
    }

Raw manifest: https://react.uipkge.dev/r/react/sidebar-02.json