Sidebar 02
block sidebarFull-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 React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/sidebar-02.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/sidebar-02.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/sidebar-02.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/sidebar-02.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/sidebar-02
Examples
npm dependencies
Used by
Files (6)
-
app/components/blocks/sidebar-02/Sidebar02.vue 2.8 kB
<script setup lang="ts"> import { BookOpen, Bot, Frame, LifeBuoy, Map, PieChart, Send, Settings2, SquareTerminal } from 'lucide-vue-next' import NavMain from './NavMain.vue' import NavProjects from './NavProjects.vue' import NavSecondary from './NavSecondary.vue' import NavUser from './NavUser.vue' import TeamSwitcher from './TeamSwitcher.vue' import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from '@/components/ui/sidebar' 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, }, ], } </script> <template> <Sidebar collapsible="icon"> <SidebarHeader> <TeamSwitcher /> </SidebarHeader> <SidebarContent> <NavMain :items="data.navMain" /> <NavProjects :projects="data.projects" /> <NavSecondary :items="data.navSecondary" class="mt-auto" /> </SidebarContent> <SidebarFooter> <NavUser :user="data.user" /> </SidebarFooter> <SidebarRail /> </Sidebar> </template> -
app/components/blocks/sidebar-02/TeamSwitcher.vue 3.9 kB
<script setup lang="ts"> // 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 { h, ref } from 'vue' import { AudioWaveform, Check, ChevronsUpDown, Command, Plus } from 'lucide-vue-next' // 4-quadrant uipkge logo. Inline functional component so it can sit // next to Lucide icons in the teams array without an extra file. const UipkgeMark = (props: { class?: string }) => h('svg', { viewBox: '0 0 24 24', class: props.class, fill: 'currentColor', 'aria-hidden': 'true' }, [ h('rect', { x: 2, y: 2, width: 9, height: 9, rx: 1.5 }), h('rect', { x: 13, y: 2, width: 9, height: 9, rx: 1.5, opacity: 0.55 }), h('rect', { x: 2, y: 13, width: 9, height: 9, rx: 1.5, opacity: 0.55 }), h('rect', { x: 13, y: 13, width: 9, height: 9, rx: 1.5 }), ]) import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar' const teams = [ { name: 'uipkge', logo: UipkgeMark, plan: 'UI registry' }, { name: 'uipkge HRMS', logo: AudioWaveform, plan: 'Vertical' }, { name: 'uipkge Hospital', logo: Command, plan: 'Vertical' }, ] const { isMobile } = useSidebar() const activeTeam = ref(teams[0]!) function setActive(team: (typeof teams)[number]) { activeTeam.value = team } </script> <template> <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger as-child> <SidebarMenuButton size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center" > <div class="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" > <component :is="activeTeam.logo" class="size-4 group-data-[collapsible=icon]:size-3.5" /> </div> <div class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span class="font-display truncate font-bold">{{ activeTeam.name }}</span> <span class="text-muted-foreground truncate text-xs">{{ activeTeam.plan }}</span> </div> <ChevronsUpDown class="ml-auto size-4 group-data-[collapsible=icon]:hidden" /> </SidebarMenuButton> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--reka-dropdown-menu-trigger-width) min-w-56 rounded-lg" :side="isMobile ? 'bottom' : 'right'" align="start" :side-offset="4" > <DropdownMenuLabel class="text-muted-foreground text-xs">Teams</DropdownMenuLabel> <DropdownMenuItem v-for="(team, i) in teams" :key="team.name" class="gap-2 p-2" @select="setActive(team)"> <div class="flex size-6 items-center justify-center rounded-sm border"> <component :is="team.logo" class="size-3.5 shrink-0" /> </div> {{ team.name }} <Check v-if="activeTeam === team" class="ml-auto size-4" /> <DropdownMenuShortcut v-else>⌘{{ i + 1 }}</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem class="gap-2 p-2"> <div class="bg-background flex size-6 items-center justify-center rounded-md border"> <Plus class="size-4" /> </div> <div class="text-muted-foreground font-medium">Add team</div> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> </template> -
app/components/blocks/sidebar-02/NavMain.vue 2 kB
<script setup lang="ts"> import type { LucideIcon } from 'lucide-vue-next' import { ChevronRight } from 'lucide-vue-next' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, } from '@/components/ui/sidebar' defineProps<{ items: { title: string url: string icon: LucideIcon isActive?: boolean items?: { title: string url: string isActive?: boolean }[] }[] }>() </script> <template> <SidebarGroup> <SidebarGroupLabel>Platform</SidebarGroupLabel> <SidebarMenu> <Collapsible v-for="item in items" :key="item.title" as-child :default-open="item.isActive"> <SidebarMenuItem> <SidebarMenuButton as-child :tooltip="item.title" :is-active="item.isActive"> <a :href="item.url"> <component :is="item.icon" /> <span>{{ item.title }}</span> </a> </SidebarMenuButton> <template v-if="item.items?.length"> <CollapsibleTrigger as-child> <SidebarMenuAction class="data-[state=open]:rotate-90"> <ChevronRight /> <span class="sr-only">Toggle</span> </SidebarMenuAction> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> <SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title"> <SidebarMenuSubButton as-child :is-active="subItem.isActive"> <a :href="subItem.url"> <span>{{ subItem.title }}</span> </a> </SidebarMenuSubButton> </SidebarMenuSubItem> </SidebarMenuSub> </CollapsibleContent> </template> </SidebarMenuItem> </Collapsible> </SidebarMenu> </SidebarGroup> </template> -
app/components/blocks/sidebar-02/NavProjects.vue 2.6 kB
<script setup lang="ts"> import type { LucideIcon } from 'lucide-vue-next' import { Folder, Forward, MoreHorizontal, Trash2 } from 'lucide-vue-next' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar, } from '@/components/ui/sidebar' defineProps<{ projects: { name: string url: string icon: LucideIcon }[] }>() const { isMobile } = useSidebar() </script> <template> <!-- Upstream shadcn-vue 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> <SidebarMenuItem v-for="item in projects" :key="item.name"> <SidebarMenuButton as-child> <a :href="item.url"> <component :is="item.icon" /> <span>{{ item.name }}</span> </a> </SidebarMenuButton> <DropdownMenu> <DropdownMenuTrigger as-child> <SidebarMenuAction show-on-hover> <MoreHorizontal /> <span class="sr-only">More</span> </SidebarMenuAction> </DropdownMenuTrigger> <DropdownMenuContent class="w-48 rounded-lg" :side="isMobile ? 'bottom' : 'right'" :align="isMobile ? 'end' : 'start'" > <DropdownMenuItem> <Folder class="text-muted-foreground" /> <span>View Project</span> </DropdownMenuItem> <DropdownMenuItem> <Forward class="text-muted-foreground" /> <span>Share Project</span> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <Trash2 class="text-muted-foreground" /> <span>Delete Project</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> <SidebarMenuItem> <SidebarMenuButton> <MoreHorizontal /> <span>More</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> </template> -
app/components/blocks/sidebar-02/NavSecondary.vue 0.8 kB
<script setup lang="ts"> import type { LucideIcon } from 'lucide-vue-next' import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, } from '@/components/ui/sidebar' const props = defineProps<{ items: { title: string url: string icon: LucideIcon }[] }>() </script> <template> <SidebarGroup> <SidebarGroupContent> <SidebarMenu> <SidebarMenuItem v-for="item in items" :key="item.title"> <SidebarMenuButton as-child size="sm" :is-active="item.isActive"> <a :href="item.url"> <component :is="item.icon" /> <span>{{ item.title }}</span> </a> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </template> -
app/components/blocks/sidebar-02/NavUser.vue 4.6 kB
<script setup lang="ts"> import { computed } from 'vue' import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogOut, Monitor, Moon, Palette, Sparkles, Sun, } from 'lucide-vue-next' import { useTheme } from '~/composables/useTheme' 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' const props = defineProps<{ user: { name: string email: string } }>() const { isMobile } = useSidebar() const { theme, setTheme } = useTheme() const initials = computed(() => { const parts = props.user.name.trim().split(/\s+/).slice(0, 2) return parts.map((p) => p[0]?.toUpperCase()).join('') || 'U' }) </script> <template> <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger as-child> <SidebarMenuButton size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center" > <Avatar class="h-8 w-8 shrink-0 rounded-lg group-data-[collapsible=icon]:size-6"> <AvatarFallback class="rounded-lg text-xs group-data-[collapsible=icon]:text-[10px]">{{ initials }}</AvatarFallback> </Avatar> <div class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span class="truncate font-medium">{{ user.name }}</span> <span class="truncate text-xs">{{ user.email }}</span> </div> <ChevronsUpDown class="ml-auto size-4 group-data-[collapsible=icon]:hidden" /> </SidebarMenuButton> </DropdownMenuTrigger> <DropdownMenuContent class="w-(--reka-dropdown-menu-trigger-width) min-w-56 rounded-lg" :side="isMobile ? 'bottom' : 'right'" align="end" :side-offset="4" > <DropdownMenuLabel class="p-0 font-normal"> <div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <Avatar class="h-8 w-8 rounded-lg"> <AvatarFallback class="rounded-lg text-xs">{{ initials }}</AvatarFallback> </Avatar> <div class="grid flex-1 text-left text-sm leading-tight"> <span class="truncate font-semibold">{{ user.name }}</span> <span class="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 class="text-muted-foreground ml-auto text-xs capitalize">{{ theme }}</span> </DropdownMenuSubTrigger> <DropdownMenuSubContent class="min-w-36"> <DropdownMenuRadioGroup :model-value="theme" @update:model-value="(v) => setTheme(v as 'light' | 'dark' | 'system')" > <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> </template>
Raw manifest: https://uipkge.dev/r/vue/sidebar-02.json