UIPackage

Dashboard Layout

block dashboard
Edit on GitHub

Full dashboard shell — collapsible sidebar (sidebar-02) + sticky topbar (sidebar trigger, breadcrumb, command palette, theme switch, notifications, profile menu) + main content slot. Drop it in a Nuxt layout: `<DashboardLayout><slot /></DashboardLayout>`. Auto-pulls every transitive piece (sidebar-02, command-palette, notifications-popover, profile-menu, theme-switch).

Also available for React ->

Installation

$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/dashboard-layout.json

Or with the named registry: npx shadcn-vue@latest add @uipkge/dashboard-layout

Examples

Schema

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

Crumb
interface Crumb {
  label: string
  href?: string
}

Files (1)

  • app/components/blocks/DashboardLayout.vue 4.1 kB
    <script setup lang="ts">
    import { Bell } from 'lucide-vue-next'
    import Sidebar02 from '@/components/blocks/sidebar-02/Sidebar02.vue'
    import CommandPalette from '@/components/blocks/CommandPalette.vue'
    import NotificationsPopover from '@/components/blocks/NotificationsPopover.vue'
    import { Button } from '@/components/ui/button'
    import { Separator } from '@/components/ui/separator'
    import {
      Breadcrumb,
      BreadcrumbItem,
      BreadcrumbLink,
      BreadcrumbList,
      BreadcrumbPage,
      BreadcrumbSeparator,
    } from '@/components/ui/breadcrumb'
    import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
    import { ThemeSwitch } from '@/components/ui/theme-switch'
    import { useTheme } from '@/composables/useTheme'
    
    interface Crumb {
      label: string
      href?: string
    }
    
    withDefaults(
      defineProps<{
        breadcrumbs?: Crumb[]
        user?: { name: string; email: string; avatar?: string }
      }>(),
      {
        breadcrumbs: () => [{ label: 'Dashboard' }],
        user: () => ({ name: 'Alex Morgan', email: '[email protected]' }),
      },
    )
    
    const emit = defineEmits<{
      (e: 'profile-select', key: string): void
      (e: 'command-select', item: { label: string; hint?: string }): void
    }>()
    
    const { theme, setTheme } = useTheme()
    </script>
    
    <template>
      <SidebarProvider>
        <Sidebar02 />
        <SidebarInset>
          <header
            class="bg-background/80 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between border-b px-4 backdrop-blur-xl transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"
          >
            <div class="flex items-center gap-2">
              <SidebarTrigger class="-ml-1" />
              <Separator orientation="vertical" class="mr-2 h-4" />
              <Breadcrumb>
                <BreadcrumbList>
                  <template v-for="(crumb, i) in breadcrumbs" :key="i">
                    <BreadcrumbItem :class="i === 0 ? 'hidden md:block' : ''">
                      <BreadcrumbLink
                        v-if="crumb.href && i < breadcrumbs.length - 1"
                        :href="crumb.href"
                        class="text-muted-foreground/70 hover:text-foreground transition-colors"
                      >
                        {{ crumb.label }}
                      </BreadcrumbLink>
                      <BreadcrumbPage v-else class="font-medium">{{ crumb.label }}</BreadcrumbPage>
                    </BreadcrumbItem>
                    <BreadcrumbSeparator v-if="i < breadcrumbs.length - 1" :class="i === 0 ? 'hidden md:block' : ''" />
                  </template>
                </BreadcrumbList>
              </Breadcrumb>
            </div>
            <div class="flex items-center gap-1 px-2 sm:gap-3">
              <CommandPalette @select="(item) => emit('command-select', item)" />
              <div class="flex items-center gap-0.5">
                <ThemeSwitch :model-value="theme" variant="icon-only" @update:model-value="setTheme" />
                <NotificationsPopover>
                  <template #default="{ unreadCount }">
                    <Button
                      variant="ghost"
                      size="icon"
                      class="text-muted-foreground hover:text-foreground relative size-8 rounded-lg"
                      aria-label="Notifications"
                    >
                      <Bell class="size-4" />
                      <span
                        v-if="unreadCount > 0"
                        class="bg-primary ring-background absolute top-1.5 right-1.5 size-2 rounded-full ring-2"
                      />
                    </Button>
                  </template>
                </NotificationsPopover>
                <!-- ProfileMenu removed from header: the sidebar's NavUser
                     (sidebar-02 footer) already owns the profile/account
                     affordance. Two avatars in the same screen pulled the
                     eye in conflicting directions. The block still accepts
                     a `user` prop + emits `profile-select` so consumers can
                     wire those into NavUser or a custom header slot. -->
              </div>
            </div>
          </header>
          <main class="flex flex-1 flex-col px-4 pt-4 pb-4 lg:px-6 lg:pt-6 lg:pb-6">
            <slot />
          </main>
        </SidebarInset>
      </SidebarProvider>
    </template>

Raw manifest: https://uipkge.dev/r/vue/dashboard-layout.json