# App 1 - Dashboard

> Simple dashboard layout with sidebar navigation and stats

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

- Page, with a live preview: https://shadcn.exchange/r/shadcnstore/app-1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcnstore/app-1-light-d80811804e3cddb5.png
- Registry item: https://shadcnstore.com/r/app-1.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnstore.com/r/app-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`, `recharts`
- Registry items: `avatar`, `badge`, `button`, `card`, `chart`, `progress`, `sidebar`

## Files

### `storage/app/registry-sources/application/apps/app-1/page.tsx`

```tsx
import { App1 } from "@/components/blocks/application/apps/app-1/components/app-1"

export default function Page() {
  return (
    <div className="flex min-h-svh w-full items-center justify-center">
      <div className="w-full">
        <App1 />
      </div>
    </div>
  )
}
```

### `storage/app/registry-sources/application/apps/app-1/app-1.tsx`

```tsx
'use client'

import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { BarChart3, Bell, ClipboardList, FileText, Search, Users } from 'lucide-react'

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
} from '@/components/ui/chart'
import { Progress } from '@/components/ui/progress'
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'

import { app1Activity, app1Chart, app1ChartConfig, app1Projects, app1User } from "@/components/data/app-1-data"
import { App1Sidebar } from './app-1-sidebar'

const STATS = [
  { label: 'Total projects', value: '24', hint: '+2 from last month', icon: FileText },
  { label: 'Active tasks', value: '127', hint: '+12 from yesterday', icon: ClipboardList },
  { label: 'Team members', value: '16', hint: '+1 new member', icon: Users },
  { label: 'Completion rate', value: '87%', hint: '+5% from last week', icon: BarChart3 },
]

const STATUS_VARIANT = {
  'In progress': 'default',
  'In review': 'secondary',
  Planning: 'outline',
} as const

export function App1() {
  return (
    <SidebarProvider>
      <App1Sidebar />
      {/*
        min-w-0: the inset is a flex item, and a flex item's min-width is `auto`, so it
        refuses to shrink below the min-content width of the widest thing inside it. At
        tablet widths, where the sidebar becomes permanent, that pushed the inset ~23px
        past the viewport and ate the right padding off every card.
      */}
      <SidebarInset className="min-w-0">
        <header className="sticky top-0 z-10 flex h-16 items-center gap-3 border-b bg-background px-4 sm:px-6">
          <SidebarTrigger aria-label="Toggle navigation" />
          <h1 className="truncate text-lg font-semibold">Welcome back, {app1User.name.split(' ')[0]}</h1>
          <div className="ml-auto flex items-center gap-1">
            <Button variant="ghost" size="icon" aria-label="Search">
              <Search />
            </Button>
            <Button variant="ghost" size="icon" aria-label="Notifications">
              <Bell />
            </Button>
            <Avatar className="ml-1 size-8">
              <AvatarImage src={app1User.avatar} alt={app1User.name} className="object-[50%_15%]" />
              <AvatarFallback>{app1User.initials}</AvatarFallback>
            </Avatar>
          </div>
        </header>

        <main className="flex flex-1 flex-col gap-6 p-4 sm:p-6">
          <div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
            {STATS.map(stat => (
              <Card key={stat.label}>
                <CardHeader>
                  <CardTitle className="text-sm font-medium">{stat.label}</CardTitle>
                  <CardAction>
                    <stat.icon aria-hidden className="size-5 text-muted-foreground" />
                  </CardAction>
                </CardHeader>
                <CardContent>
                  <p className="text-2xl font-semibold tabular-nums">{stat.value}</p>
                  <p className="text-xs text-muted-foreground">{stat.hint}</p>
                </CardContent>
              </Card>
            ))}
          </div>

          <Card>
            <CardHeader>
              <CardTitle>Task throughput</CardTitle>
              <CardDescription>Tasks opened against tasks completed over the last eight weeks</CardDescription>
            </CardHeader>
            <CardContent>
              {/* ChartContainer supplies the responsive wrapper and the CSS variables the series read. */}
              <ChartContainer config={app1ChartConfig} className="h-64 w-full">
                <AreaChart data={app1Chart} margin={{ left: 4, right: 4, top: 8 }} accessibilityLayer>
                  <defs>
                    <linearGradient id="app1Completed" x1="0" y1="0" x2="0" y2="1">
                      <stop offset="0%" stopColor="var(--color-completed)" stopOpacity={0.25} />
                      <stop offset="100%" stopColor="var(--color-completed)" stopOpacity={0.02} />
                    </linearGradient>
                  </defs>
                  <CartesianGrid vertical={false} strokeDasharray="3 3" />
                  <XAxis dataKey="week" tickLine={false} axisLine={false} tickMargin={8} />
                  <YAxis tickLine={false} axisLine={false} width={32} />
                  <ChartTooltip content={<ChartTooltipContent />} />
                  <ChartLegend content={<ChartLegendContent />} />
                  {/* The two series separate by form, not by luminance, so they stay readable in both themes. */}
                  <Area
                    dataKey="opened"
                    type="monotone"
                    stroke="var(--color-opened)"
                    strokeDasharray="4 4"
                    fill="none"
                    strokeWidth={2}
                  />
                  <Area
                    dataKey="completed"
                    type="monotone"
                    stroke="var(--color-completed)"
                    fill="url(#app1Completed)"
                    strokeWidth={2}
                  />
                </AreaChart>
              </ChartContainer>
            </CardContent>
          </Card>

          <div className="grid gap-6 lg:grid-cols-2">
            <Card>
              <CardHeader>
                <CardTitle>Active projects</CardTitle>
                <CardDescription>Your current project progress</CardDescription>
              </CardHeader>
              <CardContent className="flex flex-col gap-5">
                {app1Projects.map(project => (
                  <div key={project.id} className="flex flex-col gap-2">
                    <div className="flex items-center justify-between gap-2">
                      <h3 className="truncate text-sm font-medium">{project.name}</h3>
                      <Badge variant={STATUS_VARIANT[project.status]}>{project.status}</Badge>
                    </div>
                    <p className="text-xs text-muted-foreground">{project.description}</p>
                    <div className="flex items-center gap-3">
                      <Progress value={project.progress} aria-label={`${project.name} progress`} />
                      <span className="shrink-0 text-xs text-muted-foreground tabular-nums">{project.progress}%</span>
                    </div>
                    <div className="flex items-center justify-between gap-2">
                      <span className="flex items-center gap-1">
                        {project.team.map(member => (
                          <Avatar key={member.name} className="size-6">
                            <AvatarImage src={member.avatar} alt={member.name} className="object-[50%_15%]" />
                            <AvatarFallback className="text-[10px]">{member.initials}</AvatarFallback>
                          </Avatar>
                        ))}
                      </span>
                      <span className="text-xs text-muted-foreground">Due {project.dueDate}</span>
                    </div>
                  </div>
                ))}
              </CardContent>
            </Card>

            <Card>
              <CardHeader>
                <CardTitle>Recent activity</CardTitle>
                <CardDescription>Latest updates from your team</CardDescription>
              </CardHeader>
              <CardContent>
                <ol className="flex flex-col gap-4">
                  {app1Activity.map(entry => (
                    <li key={entry.id} className="flex items-start gap-3">
                      <Avatar className="size-8">
                        <AvatarImage src={entry.person.avatar} alt={entry.person.name} className="object-[50%_15%]" />
                        <AvatarFallback className="text-xs">{entry.person.initials}</AvatarFallback>
                      </Avatar>
                      <div className="min-w-0 flex-1">
                        <p className="text-sm leading-snug">
                          <span className="font-medium">{entry.person.name}</span>{' '}
                          <span className="text-muted-foreground">{entry.action}</span>
                        </p>
                        <p className="text-xs text-muted-foreground">{entry.time}</p>
                      </div>
                    </li>
                  ))}
                </ol>
              </CardContent>
            </Card>
          </div>
        </main>
      </SidebarInset>
    </SidebarProvider>
  )
}

export default App1
```

### `storage/app/registry-sources/application/apps/app-1/app-1-sidebar.tsx`

```tsx
'use client'

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  useSidebar,
} from '@/components/ui/sidebar'

import { app1Nav, app1Team, app1User } from "@/components/data/app-1-data"

function BrandMark({ className }: { className?: string }) {
  return (
    <svg className={className} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
      <path
        d="M26 24.75C26.4142 24.75 26.75 24.4142 26.75 24C26.75 23.5858 26.4142 23.25 26 23.25V24.75ZM26 23.25H11V24.75H26V23.25ZM8.75 21V15H7.25V21H8.75ZM11 23.25C9.75736 23.25 8.75 22.2426 8.75 21H7.25C7.25 23.0711 8.92893 24.75 11 24.75V23.25Z"
        fill="currentColor"
      />
      <path
        d="M1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4C0.75 4.41421 1.08579 4.75 1.5 4.75V3.25ZM1.5 4.75H6V3.25H1.5V4.75ZM7.25 6V21H8.75V6H7.25ZM6 4.75C6.69036 4.75 7.25 5.30964 7.25 6H8.75C8.75 4.48122 7.51878 3.25 6 3.25V4.75Z"
        fill="currentColor"
      />
      <path
        d="M22 21.75C22.4142 21.75 22.75 21.4142 22.75 21C22.75 20.5858 22.4142 20.25 22 20.25V21.75ZM22 20.25H11V21.75H22V20.25ZM8.75 18V12H7.25V18H8.75ZM11 20.25C9.75736 20.25 8.75 19.2426 8.75 18H7.25C7.25 20.0711 8.92893 21.75 11 21.75V20.25Z"
        fill="currentColor"
      />
      <path
        d="M27.2057 19.754C27.0654 20.1438 26.6357 20.346 26.246 20.2057C25.8562 20.0654 25.654 19.6357 25.7943 19.246L27.2057 19.754ZM30.0361 9.67744L29.3305 9.4234L29.3305 9.4234L30.0361 9.67744ZM25.7943 19.246L29.3305 9.4234L30.7418 9.93148L27.2057 19.754L25.7943 19.246ZM28.1543 7.75L8 7.75V6.25L28.1543 6.25V7.75ZM29.3305 9.4234C29.6237 8.60882 29.0201 7.75 28.1543 7.75V6.25C30.059 6.25 31.3869 8.13941 30.7418 9.93148L29.3305 9.4234Z"
        fill="currentColor"
      />
      <path
        d="M13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21C12.75 20.5858 13.0858 20.25 13.5 20.25V21.75ZM26.7111 19.009L27.4174 19.2613L27.4174 19.2613L26.7111 19.009ZM13.5 20.25H23.8858V21.75H13.5V20.25ZM26.0048 18.7568L27.7937 13.7477L29.2063 14.2523L27.4174 19.2613L26.0048 18.7568ZM23.8858 20.25C24.8367 20.25 25.6849 19.6522 26.0048 18.7568L27.4174 19.2613C26.8843 20.7537 25.4706 21.75 23.8858 21.75V20.25Z"
        fill="currentColor"
      />
      <path d="M21.1694 10.5806L14.5651 17.1849" stroke="currentColor" />
      <path d="M22.1694 14.5806L18.5632 18.1868" stroke="currentColor" />
      <circle cx="13.1" cy="26.1" r="1.7" stroke="currentColor" />
      <circle cx="22.1" cy="26.1" r="1.7" stroke="currentColor" />
    </svg>
  )
}

/**
 * Built on the Sidebar primitive rather than a hand-rolled fixed panel: it brings the mobile
 * sheet, the focus trap and the `sidebar_state` cookie for free. SidebarInset must stay a direct
 * sibling of Sidebar.
 */
export function App1Sidebar() {
  const { setOpenMobile } = useSidebar()

  return (
    <Sidebar>
      <SidebarHeader>
        <a
          href="#dashboard"
          className="flex min-h-11 items-center gap-2 rounded-lg px-2 font-semibold"
          onClick={() => setOpenMobile(false)}
        >
          <span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary p-1.5 text-primary-foreground">
            <BrandMark className="size-full" />
          </span>
          ShadcnStore
        </a>
      </SidebarHeader>
      <SidebarContent>
        <SidebarGroup>
          <SidebarGroupContent>
            <nav aria-label="Dashboard">
              <SidebarMenu>
                {app1Nav.map(item => (
                  <SidebarMenuItem key={item.name}>
                    <SidebarMenuButton
                      isActive={item.current}
                      render={
                        <a
                          href={item.href}
                          aria-current={item.current ? 'page' : undefined}
                          onClick={() => setOpenMobile(false)}
                        />
                      }
                    >
                      <item.icon />
                      <span>{item.name}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                ))}
              </SidebarMenu>
            </nav>
          </SidebarGroupContent>
        </SidebarGroup>
        {/* Presence fills what would otherwise be a column of dead space under seven nav links. */}
        <SidebarGroup>
          <SidebarGroupLabel>Your team</SidebarGroupLabel>
          <SidebarGroupContent>
            <SidebarMenu>
              {app1Team.map(member => (
                <SidebarMenuItem key={member.name}>
                  <SidebarMenuButton render={<a href="#team" onClick={() => setOpenMobile(false)} />}>
                    <span className="relative shrink-0">
                      <Avatar className="size-6">
                        <AvatarImage src={member.avatar} alt="" className="object-[50%_15%]" />
                        <AvatarFallback className="text-[10px]">{member.initials}</AvatarFallback>
                      </Avatar>
                      {member.online ? (
                        <span
                          aria-hidden
                          className="absolute right-0 bottom-0 size-2 rounded-full border-2 border-sidebar bg-primary"
                        />
                      ) : null}
                    </span>
                    <span className="truncate">{member.name}</span>
                    <span className="ml-auto shrink-0 text-xs opacity-70">{member.role}</span>
                  </SidebarMenuButton>
                </SidebarMenuItem>
              ))}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter>
        <SidebarMenu>
          <SidebarMenuItem>
            <SidebarMenuButton className="min-h-12">
              <Avatar className="size-7">
                <AvatarImage src={app1User.avatar} alt="" className="object-[50%_15%]" />
                <AvatarFallback>{app1User.initials}</AvatarFallback>
              </Avatar>
              <span className="min-w-0 flex-1 text-left">
                <span className="block truncate font-medium">{app1User.name}</span>
                <span className="block truncate text-xs opacity-70">{app1User.email}</span>
              </span>
            </SidebarMenuButton>
          </SidebarMenuItem>
        </SidebarMenu>
      </SidebarFooter>
    </Sidebar>
  )
}

export default App1Sidebar
```

### `storage/app/registry-sources/application/apps/app-1/app-1-data.ts`

```ts
import type { LucideIcon } from 'lucide-react'
import { BarChart3, Calendar, FileText, Home, MessageSquare, Settings, Users } from 'lucide-react'

import type { ChartConfig } from '@/components/ui/chart'

/**
 * Portraits are served from the ShadcnStore CDN rather than a third-party avatar API, so a
 * shipped block never depends on someone else's host staying up. Every Avatar keeps an initials
 * fallback for when the image is blocked or offline.
 */
const CDN = 'https://assets.shadcnstore.com/shadcnstore.com/stock/marketing'

export interface App1Person {
  name: string
  initials: string
  avatar: string
}

export const app1People = {
  jordan: { name: 'Jordan Diaz', initials: 'JD', avatar: `${CDN}/robert-chen.400w.e979c2.avif` },
  john: { name: 'John Doe', initials: 'JD', avatar: `${CDN}/david-kim.400w.a3611d.avif` },
  sarah: { name: 'Sarah Kim', initials: 'SK', avatar: `${CDN}/alice-brian.400w.fa3d8c.avif` },
  mike: { name: 'Mike Taylor', initials: 'MT', avatar: `${CDN}/emily-chen.400w.8fb3cf.avif` },
  lisa: { name: 'Lisa Park', initials: 'LP', avatar: `${CDN}/jennifer-park.400w.d30794.avif` },
  ravi: { name: 'Ravi Singh', initials: 'RS', avatar: `${CDN}/john-david.400w.fde889.avif` },
  emily: { name: 'Emily Chen', initials: 'EC', avatar: `${CDN}/michael-rodriguez.400w.d8234b.avif` },
  alex: { name: 'Alex Moore', initials: 'AM', avatar: `${CDN}/susan-d-lunsford.400w.a3e5c2.avif` },
  ryan: { name: 'Ryan Wilson', initials: 'RW', avatar: `${CDN}/keneth-conroy.400w.c4ea19.avif` },
  priya: { name: 'Priya Shah', initials: 'PS', avatar: `${CDN}/sarah-johnson.400w.01a6ba.avif` },
} satisfies Record<string, App1Person>

export interface App1NavItem {
  name: string
  href: string
  icon: LucideIcon
  current: boolean
}

export interface App1Project {
  id: string
  name: string
  description: string
  progress: number
  status: 'In progress' | 'Planning' | 'In review'
  team: App1Person[]
  dueDate: string
}

export interface App1ActivityItem {
  id: string
  person: App1Person
  action: string
  time: string
}

export const app1User = { ...app1People.jordan, email: 'jordan@shadcnstore.com' }

/** Five teammates, so the sidebar carries presence instead of a column of dead space. */
export const app1Team: (App1Person & { role: string; online: boolean })[] = [
  { ...app1People.sarah, role: 'Design', online: true },
  { ...app1People.john, role: 'Engineering', online: true },
  { ...app1People.mike, role: 'Engineering', online: false },
  { ...app1People.lisa, role: 'Product', online: true },
  { ...app1People.ravi, role: 'Data', online: false },
]

export const app1Nav: App1NavItem[] = [
  { name: 'Dashboard', href: '#dashboard', icon: Home, current: true },
  { name: 'Analytics', href: '#analytics', icon: BarChart3, current: false },
  { name: 'Team', href: '#team', icon: Users, current: false },
  { name: 'Messages', href: '#messages', icon: MessageSquare, current: false },
  { name: 'Documents', href: '#documents', icon: FileText, current: false },
  { name: 'Calendar', href: '#calendar', icon: Calendar, current: false },
  { name: 'Settings', href: '#settings', icon: Settings, current: false },
]

/** Tasks opened against tasks closed, so the chart shows a backlog trend rather than one flat line. */
export const app1Chart = [
  { week: 'Wk 1', opened: 34, completed: 28 },
  { week: 'Wk 2', opened: 41, completed: 33 },
  { week: 'Wk 3', opened: 29, completed: 37 },
  { week: 'Wk 4', opened: 45, completed: 39 },
  { week: 'Wk 5', opened: 38, completed: 44 },
  { week: 'Wk 6', opened: 52, completed: 46 },
  { week: 'Wk 7', opened: 47, completed: 51 },
  { week: 'Wk 8', opened: 43, completed: 55 },
]

export const app1ChartConfig = {
  // Semantic tokens, not chart-N: the neutral palette's chart ramp has too little contrast to tell
  // two series apart, and these two flip correctly in both themes.
  completed: { label: 'Completed', color: 'var(--foreground)' },
  opened: { label: 'Opened', color: 'var(--muted-foreground)' },
} satisfies ChartConfig

export const app1Projects: App1Project[] = [
  {
    id: 'p-1',
    name: 'Mobile app redesign',
    description: 'Complete UI and UX overhaul',
    progress: 75,
    status: 'In progress',
    team: [app1People.jordan, app1People.alex, app1People.sarah],
    dueDate: 'Dec 15',
  },
  {
    id: 'p-2',
    name: 'Dashboard analytics',
    description: 'New reporting features',
    progress: 45,
    status: 'Planning',
    team: [app1People.ryan, app1People.mike],
    dueDate: 'Jan 20',
  },
  {
    id: 'p-3',
    name: 'API integration',
    description: 'Third-party services',
    progress: 90,
    status: 'In review',
    team: [app1People.john, app1People.lisa, app1People.priya],
    dueDate: 'Dec 10',
  },
  {
    id: 'p-4',
    name: 'Billing migration',
    description: 'Move invoices to the new provider',
    progress: 30,
    status: 'In progress',
    team: [app1People.emily, app1People.mike],
    dueDate: 'Feb 2',
  },
]

export const app1Activity: App1ActivityItem[] = [
  { id: 'a-1', person: app1People.john, action: 'completed task "User authentication"', time: '2 hours ago' },
  { id: 'a-2', person: app1People.sarah, action: 'commented on "Mobile design"', time: '4 hours ago' },
  { id: 'a-3', person: app1People.mike, action: 'uploaded 3 files to the project', time: '6 hours ago' },
  { id: 'a-4', person: app1People.lisa, action: 'updated the project timeline', time: '8 hours ago' },
  { id: 'a-5', person: app1People.ravi, action: 'created the project "Website revamp"', time: '10 hours ago' },
  { id: 'a-6', person: app1People.emily, action: 'assigned a task to Mike Taylor', time: '12 hours ago' },
]
```
