# Dashboard 1

> Dashboard 1 variant for block.

A shadcn/ui block from the [@uiable](https://uiable.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiable/block-dashboard-1).

- Page, with a live preview: https://shadcn.exchange/r/uiable/block-dashboard-1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiable/block-dashboard-1-light-bd79a07473a6cd2e.png
- Registry item: https://uiable.com/r/block-dashboard-1.json
- Categories: dashboard-layout
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiable.com/r/block-dashboard-1.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `next-themes`
- Registry items: `@uiable/avatar`, `@uiable/button`, `@uiable/card`, `@uiable/collapsible`, `@uiable/logo`, `@uiable/notification-dropdown`, `@uiable/search-bar`, `@uiable/sidebar`, `@uiable/user-profile`

## Files

### `src/components/uiable/blocks/layout/dashboard-layout/dashboard-1.tsx`

```tsx
"use client"

import { ReactNode, useEffect, useState } from "react"

// next
import Link from "next/link"
import { usePathname } from "next/navigation"

// shadcn
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import {
  Sidebar,
  SidebarContent,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInset,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarRail,
  SidebarTrigger,
} from "@/components/ui/sidebar"

// third-party
import { useTheme } from "next-themes"

// project-imports
import branding from "@/branding.json"
import {
  hasActiveDescendant,
  isNavItemActive,
  menuGroups,
  type NavGroup,
  type NavItem,
} from "@/components/uiable/blocks/layout/dashboard-layout/data/dashboard-1.data"
import NotificationDropdown from "@/components/uiable/layout/notification-dropdown"
import SearchBar from "@/components/uiable/layout/search-bar"
import Logo from "@/components/uiable/layout/shared/logo"
import UserProfile from "@/components/uiable/layout/user-profile"

// assets
import {
  ChevronDown,
  ChevronRight,
  LockKeyhole,
  LogOut,
  Moon,
  Power,
  Settings,
  Sun,
  User,
} from "lucide-react"

const imgCoupon = {
  src: "https://cdn.uiable.com/application/img-coupon.png",
}
const uImg1 = {
  src: "https://cdn.uiable.com/user/avatar-1.jpg",
}

const menuButtonClass =
  "rounded-lg px-5 py-3.5 text-foreground/70 data-[active=true]:bg-primary/10 data-[active=true]:text-primary"
const subMenuButtonClass =
  "h-8 px-5 py-3.5 text-foreground/70 hover:bg-muted-foreground/6 hover:text-foreground hover:after:bg-foreground hover:after:opacity-100 data-[active=true]:text-primary"
const groupLabelClass =
  "px-5.5 pt-6 pb-2 text-[10px] font-bold tracking-widest text-muted-foreground/70 uppercase"

//  ------------------------------ | USER PROFILE CARD | ------------------------------  //

function UserProfileCard() {
  const [isUserOpen, setIsUserOpen] = useState(false)

  return (
    <Card className="m-1.75 bg-muted shadow-none">
      <CardContent className="p-5!">
        <Collapsible
          open={isUserOpen}
          onOpenChange={setIsUserOpen}
          className="flex flex-col gap-2"
        >
          <div className="flex items-center gap-3">
            <Avatar className="h-10 w-10 border-2 border-background">
              <AvatarImage src={uImg1.src} alt="User" />
              <AvatarFallback className="bg-primary/10 font-bold text-primary">
                JS
              </AvatarFallback>
            </Avatar>
            <div className="flex flex-1 flex-col">
              <h6 className="mb-0">John Smith</h6>
              <small className="text-muted-foreground">Administrator</small>
            </div>
            <CollapsibleTrigger
              render={
                <Button variant="ghost" size="icon">
                  <ChevronDown className="size-5.5 transition-transform duration-300 ease-out group-data-panel-open/button:rotate-180" />
                </Button>
              }
            />
          </div>
          <CollapsibleContent className="h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-300 ease-out data-ending-style:h-0 data-starting-style:h-0">
            <div className="pt-3 *:flex *:items-center *:gap-2.5 *:py-2 *:last:pb-0 *:hover:text-primary">
              <a href="#!">
                <User className="size-4" />
                <span>My Account</span>
              </a>
              <a href="#!">
                <Settings className="size-4" />
                <span>Settings</span>
              </a>
              <a href="#!">
                <LockKeyhole className="size-4" />
                <span>Lock Screen</span>
              </a>
              <Link href="/login">
                <Power className="size-4" />
                <span>Logout</span>
              </Link>
            </div>
          </CollapsibleContent>
        </Collapsible>
      </CardContent>
    </Card>
  )
}

//  ------------------------------ | NESTED COLLAPSIBLE CLASSES | ------------------------------  //

// Static class strings are listed literally so Tailwind can detect them. Each
// collapsible node is mapped to one entry in DFS pre-order (see below).
const collapsibleGroups = [
  {
    group: "group/collapsible",
    rotate: "group-data-open/collapsible:rotate-90",
  },
  {
    group: "group/collapsible-2",
    rotate: "group-data-open/collapsible-2:rotate-90",
  },
  {
    group: "group/collapsible-3",
    rotate: "group-data-open/collapsible-3:rotate-90",
  },
  {
    group: "group/collapsible-4",
    rotate: "group-data-open/collapsible-4:rotate-90",
  },
  {
    group: "group/collapsible-5",
    rotate: "group-data-open/collapsible-5:rotate-90",
  },
]

// Assign a unique static group class to every collapsible node (any item with
// children) once, walking all groups in DFS pre-order so nested rotations stay
// scoped to their own trigger.
const collapsibleClassByItem = new Map<
  NavItem,
  (typeof collapsibleGroups)[number]
>()
;(() => {
  let index = 0
  const assign = (item: NavItem) => {
    if (!item.children) return
    collapsibleClassByItem.set(item, collapsibleGroups[index++])
    item.children.forEach(assign)
  }
  menuGroups.forEach((group) => group.items.forEach(assign))
})()

//  ------------------------------ | NAV LEAF ITEM | ------------------------------  //

function NavLeafItem({ item }: { item: NavItem }) {
  const pathname = usePathname()
  const Icon = item.icon
  return (
    <SidebarMenuItem>
      <SidebarMenuButton
        isActive={isNavItemActive(pathname, item.href)}
        className={menuButtonClass}
        render={
          <Link
            href={item.href ?? "#"}
            target={item.target}
            rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
          />
        }
      >
        {Icon ? <Icon className="size-4.5!" /> : null}
        <span className="font-medium">{item.title}</span>
      </SidebarMenuButton>
    </SidebarMenuItem>
  )
}

//  ------------------------------ | NAV SUB NODE (NESTED) | ------------------------------  //

function NavSubNode({ node }: { node: NavItem }) {
  const pathname = usePathname()

  if (!node.children) {
    return (
      <SidebarMenuSubItem>
        <SidebarMenuSubButton
          isActive={isNavItemActive(pathname, node.href)}
          className={subMenuButtonClass}
          render={<Link href={node.href ?? "#"} />}
        >
          <span>{node.title}</span>
        </SidebarMenuSubButton>
      </SidebarMenuSubItem>
    )
  }

  const { group, rotate } = collapsibleClassByItem.get(node)!
  return (
    <Collapsible
      className={group}
      defaultOpen={hasActiveDescendant(node, pathname)}
    >
      <SidebarMenuSubItem>
        <CollapsibleTrigger
          nativeButton={false}
          render={
            <SidebarMenuSubButton
              className={`flex w-full justify-between ${subMenuButtonClass}`}
            />
          }
        >
          <span>{node.title}</span>
          <ChevronRight
            className={`ml-auto size-4 transition-transform ${rotate}`}
          />
        </CollapsibleTrigger>
        <CollapsibleContent className="h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-300 ease-out data-ending-style:h-0 data-starting-style:h-0">
          <SidebarMenuSub className="mt-1 ml-2 border-l border-border/50 pl-2">
            {node.children.map((child) => (
              <NavSubNode key={child.id} node={child} />
            ))}
          </SidebarMenuSub>
        </CollapsibleContent>
      </SidebarMenuSubItem>
    </Collapsible>
  )
}

//  ------------------------------ | NAV COLLAPSIBLE ITEM (TOP-LEVEL) | ------------------------------  //

function NavCollapsibleItem({ item }: { item: NavItem }) {
  const pathname = usePathname()
  const { group, rotate } = collapsibleClassByItem.get(item)!
  const Icon = item.icon
  return (
    <Collapsible
      className={group}
      defaultOpen={hasActiveDescendant(item, pathname)}
    >
      <SidebarMenuItem>
        <CollapsibleTrigger
          render={
            <SidebarMenuButton
              tooltip={item.title}
              className="rounded-lg px-5 py-3.5 text-foreground/70"
            />
          }
        >
          {Icon ? <Icon className="size-4.5!" /> : null}
          <span className="font-medium">{item.title}</span>
          <ChevronRight
            className={`ml-auto size-4 transition-transform ${rotate}`}
          />
        </CollapsibleTrigger>
        <CollapsibleContent className="h-(--collapsible-panel-height) overflow-hidden transition-[height] duration-300 ease-out data-ending-style:h-0 data-starting-style:h-0">
          <SidebarMenuSub>
            {item.children?.map((child) => (
              <NavSubNode key={child.id} node={child} />
            ))}
          </SidebarMenuSub>
        </CollapsibleContent>
      </SidebarMenuItem>
    </Collapsible>
  )
}

//  ------------------------------ | NAV GROUP SECTION | ------------------------------  //

function NavGroupSection({ group }: { group: NavGroup }) {
  return (
    <SidebarGroup>
      <SidebarGroupLabel className={groupLabelClass}>
        {group.label}
      </SidebarGroupLabel>
      <SidebarGroupContent>
        <SidebarMenu>
          {group.items.map((item) =>
            item.children?.length ? (
              <NavCollapsibleItem key={item.id} item={item} />
            ) : (
              <NavLeafItem key={item.id} item={item} />
            )
          )}
        </SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
  )
}

//  ------------------------------ | UPGRADE CARD | ------------------------------  //

function UpgradeCard() {
  return (
    <Card className="mx-1.75 my-3.75 shadow-none">
      <CardContent className="text-center">
        <img
          src={imgCoupon.src}
          alt="Coupon"
          className="mx-auto w-2/4 max-w-full"
        />
        <h5 className="mt-1 mb-0">{branding.brandName}</h5>
        <p className="mb-4">Checkout pro features</p>
        <Button className="gap-2 bg-yellow-500 hover:bg-yellow-600">
          <LogOut />
          Upgrade to Pro
        </Button>
      </CardContent>
    </Card>
  )
}

//  ------------------------------ | DASHBOARD SIDEBAR | ------------------------------  //

function DashboardSidebar() {
  return (
    <Sidebar
      collapsible="offcanvas"
      variant="inset"
      className="absolute z-10 h-full border-r border-dashed"
    >
      <SidebarHeader className="px-4 pt-2 pb-4">
        <SidebarMenu>
          <SidebarMenuItem>
            <Logo link={false} />
          </SidebarMenuItem>
        </SidebarMenu>
      </SidebarHeader>

      <SidebarContent className="gap-0 px-2 *:py-0">
        <UserProfileCard />
        {menuGroups.map((group) => (
          <NavGroupSection key={group.id} group={group} />
        ))}
        <UpgradeCard />
      </SidebarContent>
      <SidebarRail />
    </Sidebar>
  )
}

//  ------------------------------ | DASHBOARD HEADER | ------------------------------  //

function DashboardHeader() {
  const { resolvedTheme, setTheme } = useTheme()
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    // eslint-disable-next-line react-hooks/set-state-in-effect
    setMounted(true)
  }, [])

  return (
    <header className="flex h-16 w-full shrink-0 items-center gap-1 bg-background/80 px-4 backdrop-blur-md">
      <SidebarTrigger className="relative mx-1 -ml-1 flex h-11 w-11 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-primary [&>svg]:size-5" />
      <SearchBar />
      <div className="flex-1" />
      <div className="flex items-center gap-1 sm:gap-2">
        <Button
          variant="ghost"
          onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
          className="inline-flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-muted-foreground transition-colors hover:bg-muted hover:text-primary"
        >
          {mounted && resolvedTheme === "dark" ? (
            <Sun className="size-5" />
          ) : (
            <Moon className="size-5" />
          )}
        </Button>
        <Button
          variant="ghost"
          className="inline-flex h-11 w-11 items-center justify-center rounded-lg border border-transparent text-muted-foreground transition-colors hover:bg-muted hover:text-primary"
        >
          <Settings className="size-5" />
        </Button>
        <div className="h-4 w-px bg-border" />
        <NotificationDropdown />
        <div className="h-4 w-px bg-border" />
        <UserProfile />
      </div>
    </header>
  )
}

//  ------------------------------ | DASHBOARD CONTENT | ------------------------------  //

function DashboardContent() {
  return (
    <div className="flex-1 overflow-y-auto">
      <div className="flex h-full min-h-0 flex-col gap-4 p-4">
        <div className="grid auto-rows-min gap-4 md:grid-cols-3">
          <div className="aspect-video rounded-xl bg-card" />
          <div className="aspect-video rounded-xl bg-card" />
          <div className="aspect-video rounded-xl bg-card" />
        </div>
        <div className="min-h-64 flex-1 rounded-xl bg-card" />
      </div>
    </div>
  )
}

//  ------------------------------ | BLOCK - DASHBOARD LAYOUT 1 (SIDEBAR) | ------------------------------  //

export default function DashboardOne({ children }: { children?: ReactNode }) {
  return (
    <SidebarProvider className="relative h-screen min-h-0! w-full overflow-hidden bg-background has-[data-slot='sidebar-inset']:shadow-none [.in-iframe-preview_&]:h-[600px]">
      <DashboardSidebar />

      {/* Main content area with navbar */}
      <SidebarInset className="flex flex-col overflow-hidden md:peer-data-[variant=inset]:m-0 md:peer-data-[variant=inset]:rounded-none">
        <DashboardHeader />
        {children ? (
          <main className="flex-1 overflow-y-auto p-4">{children}</main>
        ) : (
          <DashboardContent />
        )}
      </SidebarInset>
    </SidebarProvider>
  )
}
```

### `src/branding.json`

```json
{
  "brandName": "UIAble",
  "title": "Components to Complete Interfaces",
  "company": {
    "name": "CodedThemes ",
    "url": "https://codedthemes.com/",
    "socialLink": {
      "linkedin": "https://in.linkedin.com/company/codedthemes",
      "x": "https://x.com/uiable",
      "instagram": "#",
      "github": "https://github.com/codedthemes/uiable",
      "discord": "https://discord.com/invite/txPuEn5atw",
      "support": "https://uiable.com/account/support"
    }
  },
  "logo": {
    "main": "",
    "logoIcon": "",
    "logoFab": "",
    "logoWatermark": ""
  }
}
```

### `src/components/uiable/blocks/layout/dashboard-layout/data/dashboard-1.data.ts`

```ts
// assets
import {
  Bell,
  BookOpen,
  ChartColumn,
  CircleX,
  Clock,
  FileText,
  Key,
  LayoutDashboard,
  Lock,
  type LucideIcon,
  Mail,
  Network,
  LayoutGrid,
  PanelLeft,
  PanelTop,
  Shield,
  ShieldCheck,
  TriangleAlert,
  UserPlus,
  Users,
  Wrench,
} from "lucide-react"

//  ------------------------------ | TYPES | ------------------------------  //

// A nav node is either a leaf (has `href`) or a parent (has `children`). Icons
// and targets are optional so nested levels can render as plain entries.
export type NavItem = {
  id: string
  title: string
  icon?: LucideIcon
  href?: string
  target?: "_blank" | "_self" | "_parent" | "_top"
  children?: NavItem[]
}

// A labelled section of the menu. The sidebar layout renders the label as a
// group heading; the top-nav layout renders it as a top-level dropdown trigger.
export type NavGroup = {
  id: string
  label: string
  icon?: LucideIcon
  items: NavItem[]
}

//  ------------------------------ | ACTIVE MENU-ITEM LOGIC | ------------------------------  //

// A link is active when the current pathname is the link itself or a nested
// route below it (e.g. `/menu-level/2-2/3-1` keeps "Level 2.2" active).
export function isNavItemActive(pathname: string, href?: string): boolean {
  if (!href) return false
  return pathname === href || pathname.startsWith(`${href}/`)
}

// A parent node is active/open when any descendant leaf matches the current
// route, so dropdown triggers and collapsibles can highlight and expand.
export function hasActiveDescendant(item: NavItem, pathname: string): boolean {
  if (isNavItemActive(pathname, item.href)) return true
  return (item.children ?? []).some((child) =>
    hasActiveDescendant(child, pathname)
  )
}

//  ------------------------------ | MENU GROUPS | ------------------------------  //

// Single source of truth for the dashboard navigation, shared by both the
// sidebar layout (dashboard-1) and the top-nav layout (dashboard-2).
export const menuGroups: NavGroup[] = [
  {
    id: "dashboard",
    label: "Dashboard",
    icon: LayoutDashboard,
    items: [
      {
        id: "dashboard",
        title: "Default",
        icon: LayoutDashboard,
        href: "/dashboard/default",
      },
      {
        id: "finance",
        title: "Finance",
        icon: ChartColumn,
        href: "/dashboard/finance",
      },
      {
        id: "lms",
        title: "LMS",
        icon: BookOpen,
        href: "/dashboard/lms",
      },
    ],
  },
  {
    id: "layout",
    label: "Layout",
    icon: PanelTop,
    items: [
      {
        id: "vertical-layout",
        title: "Vertical",
        icon: PanelLeft,
        href: "/layout/vertical",
      },
      {
        id: "horizontal-layout",
        title: "Horizontal",
        icon: PanelTop,
        href: "/layout/horizontal",
      },
      {
        id: "icon-rail-layout",
        title: "Icon Rail Split",
        icon: LayoutGrid,
        href: "/layout/icon-rail",
      },
    ],
  },
  {
    id: "authentication",
    label: "Authentication",
    icon: Shield,
    items: [
      { id: "login", title: "Login", icon: Shield, href: "/login" },
      { id: "register", title: "Register", icon: UserPlus, href: "/register" },
      {
        id: "forgot-password",
        title: "Forgot Password",
        icon: Key,
        href: "/forgot-password",
      },
      {
        id: "reset-password",
        title: "Reset Password",
        icon: Lock,
        href: "/reset-password",
      },
      {
        id: "check-mail",
        title: "Check Mail",
        icon: Mail,
        href: "/check-mail",
      },
      {
        id: "code-verification",
        title: "Code Verification",
        icon: ShieldCheck,
        href: "/code-verification",
      },
    ].map((item) => ({ ...item, target: "_blank" as const })),
  },
  {
    id: "maintenance",
    label: "Maintenance",
    icon: Wrench,
    items: [
      {
        id: "error-404",
        title: "Error 404",
        icon: CircleX,
        href: "/error-404",
      },
      {
        id: "error-500",
        title: "Error 500",
        icon: TriangleAlert,
        href: "/error-500",
      },
      {
        id: "coming-soon",
        title: "Coming Soon",
        icon: Clock,
        href: "/coming-soon",
      },
      {
        id: "under-construction",
        title: "Under Construction",
        icon: Wrench,
        href: "/under-construction",
      },
      {
        id: "join-waitlist",
        title: "Join Waitlist",
        icon: Users,
        href: "/join-waitlist",
      },
    ].map((item) => ({ ...item, target: "_blank" as const })),
  },
  {
    id: "extra",
    label: "Extra",
    icon: Bell,
    items: [
      {
        id: "docs",
        title: "Documentation",
        icon: FileText,
        href: "https://uiable.com/doc/admin-dashboard",
        target: "_blank",
      },
      // Leaf nodes carry a unique `href`; parent nodes derive their open state
      // from whether any descendant leaf matches the current route.
      {
        id: "menu-levels",
        title: "Menu levels",
        icon: Network,
        children: [
          {
            id: "level-2-1",
            title: "Level 2.1",
            href: "#",
          },
          {
            id: "level-2-2",
            title: "Level 2.2",
            children: [
              {
                id: "level-2-2-3-1",
                title: "Level 3.1",
                href: "#",
              },
              {
                id: "level-2-2-3-2",
                title: "Level 3.2",
                href: "#",
              },
              {
                id: "level-2-2-3-3",
                title: "Level 3.3",
                children: [
                  {
                    id: "level-2-2-3-3-4-1",
                    title: "Level 4.1",
                    href: "#",
                  },
                  {
                    id: "level-2-2-3-3-4-2",
                    title: "Level 4.2",
                    href: "#",
                  },
                ],
              },
            ],
          },
          {
            id: "level-2-3",
            title: "Level 2.3",
            children: [
              {
                id: "level-2-3-3-1",
                title: "Level 3.1",
                href: "#",
              },
              {
                id: "level-2-3-3-2",
                title: "Level 3.2",
                href: "#",
              },
              {
                id: "level-2-3-3-3",
                title: "Level 3.3",
                children: [
                  {
                    id: "level-2-3-3-3-4-1",
                    title: "Level 4.1",
                    href: "#",
                  },
                  {
                    id: "level-2-3-3-3-4-2",
                    title: "Level 4.2",
                    href: "#",
                  },
                ],
              },
            ],
          },
        ],
      },
      { id: "sample", title: "Sample Page", icon: Bell, href: "/sample-page" },
    ],
  },
]
```
