Dashboard Setup — Developer Guide
Feature: F0.2 — Next.js Dashboard Scaffold
Status: Completed
App:apps/dashboard(Next.js 15, App Router)
Overview
The dashboard is a Next.js 15 App Router application. It uses:
- Tailwind CSS 4 with custom CSS variables for theming
- shadcn/ui (New York style) for all UI primitives
- next-themes for dark/light mode persistence
- Geist Sans + Geist Mono for typography (matches Vercel/Linear aesthetic)
The layout uses a two-panel structure: a fixed sidebar on the left and a scrollable main content area on the right. On mobile, the sidebar collapses and is accessible via a Sheet drawer triggered by a hamburger button in the header.
Project Structure
apps/dashboard/
├── src/
│ ├── app/
│ │ ├── globals.css # Tailwind + all CSS theme variables
│ │ ├── layout.tsx # Root layout: fonts, ThemeProvider
│ │ ├── page.tsx # Redirects / → /dashboard
│ │ └── (panel)/
│ │ ├── layout.tsx # Sidebar + Header shell
│ │ └── dashboard/
│ │ └── page.tsx # Dashboard home page
│ ├── components/
│ │ ├── ui/ # shadcn/ui primitives (DO NOT edit manually)
│ │ │ ├── button.tsx
│ │ │ ├── card.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ └── skeleton.tsx
│ │ ├── layout/
│ │ │ ├── Sidebar.tsx # Desktop sidebar navigation
│ │ │ ├── Header.tsx # Top bar (mobile trigger + theme toggle)
│ │ │ ├── MobileSidebar.tsx # Sheet-wrapped Sidebar for mobile
│ │ │ └── ThemeToggle.tsx # Sun/Moon dark mode button
│ │ ├── dashboard/
│ │ │ └── StatCard.tsx # Reusable metric card with loading state
│ │ └── providers/
│ │ └── ThemeProvider.tsx # next-themes client wrapper
│ └── lib/
│ └── utils.ts # cn() helper (clsx + tailwind-merge)
├── components.json # shadcn/ui configuration
├── next.config.ts # transpilePackages: @vexlyx/shared
├── postcss.config.mjs # @tailwindcss/postcss plugin
└── package.jsonTheme System
All colors are defined as CSS variables in globals.css. Never hardcode color values — always use the semantic tokens.
Available Tokens
| Token | Use Case |
|---|---|
bg-background | Page background |
bg-card | Card / panel surfaces |
text-foreground | Primary text |
text-muted-foreground | Secondary / label text |
bg-primary / text-primary | Brand actions, active states |
bg-destructive | Delete / danger actions |
border-border | Borders and dividers |
bg-sidebar / text-sidebar-foreground | Sidebar-specific surfaces |
bg-sidebar-accent | Sidebar hover/active states |
Dark mode is handled automatically via the .dark class on <html> (set by next-themes).
Adding a New Page
Create the page file under the
(panel)route group:src/app/(panel)/your-feature/page.tsxExport a default React component:
tsxexport default function YourFeaturePage() { return <div>...</div>; }Add a navigation link in
Sidebar.tsx:tsxconst navItems = [ // ...existing items { label: "Your Feature", href: "/your-feature", icon: YourIcon }, ];
The page will automatically inherit the sidebar + header layout from (panel)/layout.tsx.
Adding shadcn/ui Components
The project uses shadcn/ui components via the radix-ui unified package (shadcn v4+). To add a new component:
cd apps/dashboard
npx shadcn@latest add [component-name]Import from @/components/ui/[component-name]. Do not edit files in src/components/ui/ — they are regenerated by shadcn.
Currently installed components: button, card, separator, sheet, skeleton.
Dark Mode
Dark mode uses next-themes with the class strategy. The ThemeProvider wraps the entire app in layout.tsx. suppressHydrationWarning is set on <html> to prevent hydration mismatches.
The ThemeToggle button (Sun/Moon icons) is rendered in the Header and persists the user's choice across sessions via localStorage.
StatCard Component
StatCard is a reusable metric display for the dashboard overview. It supports a loading state that renders skeleton placeholders instead of values.
import { StatCard } from "@/components/dashboard/StatCard";
import { Server } from "lucide-react";
// Normal state
<StatCard title="Active Projects" value="12" description="+2 this week" icon={Server} />
// Loading state (show skeletons while data fetches)
<StatCard title="Active Projects" icon={Server} isLoading />Mobile Behavior
On screens narrower than md (768px):
- The desktop sidebar (
<aside className="hidden md:flex">) is hidden - The
Headershows a hamburger button (MobileSidebar) - Clicking it opens the
Sidebarinside aSheet(slide-in drawer from the left) - Clicking any navigation link closes the sheet automatically
Running Locally
# From monorepo root
pnpm dev # Starts dashboard on http://localhost:3000
# From apps/dashboard only
pnpm dev # Same, but without other packages
pnpm typecheck # TypeScript strict check
pnpm lint # ESLint check
pnpm build # Production build (must pass before any PR)