SaaS Dashboard
A comprehensive admin dashboard for SaaS applications with analytics, user management, activity tracking, and revenue metrics. Built for scale.
Overview
Built for Modern SaaS
This dashboard template provides a comprehensive admin interface for SaaS applications. Track revenue, manage users, monitor activity, and gain insights with beautiful visualizations and intuitive navigation.
Real-time Analytics
Interactive charts and metrics with live data updates
User Management
Customer table with filtering, status badges, and actions
Revenue Tracking
Monthly revenue charts with comparison indicators
Activity Feed
Real-time notifications and user activity timeline
Live Demo
Dashboard
Welcome back, John. Here's what's happening.
Revenue Overview
Recent Activity
Sarah A. upgraded to Pro plan
2 min ago
New user signed up for trial
15 min ago
Michael C. submitted support ticket
1 hour ago
Emily J. completed onboarding
2 hours ago
Recent Customers
| Customer | Plan | Status | Actions |
|---|---|---|---|
SA Sarah Anderson sarah@example.com | Pro | active | |
MC Michael Chen michael@example.com | Enterprise | active | |
EJ Emily Johnson emily@example.com | Starter | pending | |
DK David Kim david@example.com | Pro | active | |
LW Lisa Wang lisa@example.com | Starter | inactive |
Implementation Guide
Set Up Project Structure
Create the dashboard layout with sidebar and main content area.
12345678910111213141516171819// Recommended folder structure src/ ├── app/ │ └── dashboard/ │ ├── layout.tsx # Dashboard layout with sidebar │ ├── page.tsx # Main dashboard │ ├── analytics/page.tsx │ ├── customers/page.tsx │ └── settings/page.tsx ├── components/ │ ├── dashboard/ │ │ ├── Sidebar.tsx │ │ ├── Header.tsx │ │ ├── StatsCard.tsx │ │ └── ActivityFeed.tsx │ └── charts/ │ └── RevenueChart.tsx └── lib/ └── dashboard-context.tsx
Create Dashboard Layout
Build the base layout with collapsible sidebar and header.
123456789101112131415161718192021222324252627"use client"; import { useState } from "react"; import { Sidebar } from "@/components/dashboard/Sidebar"; import { Header } from "@/components/dashboard/Header"; export default function DashboardLayout({ children }: { children: React.ReactNode }) { const [sidebarOpen, setSidebarOpen] = useState(true); return ( <div className="min-h-screen bg-slate-50 dark:bg-slate-950 flex"> <Sidebar isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} /> <div className="flex-1 flex flex-col"> <Header onMenuClick={() => setSidebarOpen(!sidebarOpen)} /> <main className="flex-1 p-6"> {children} </main> </div> </div> ); }
Build Stats Cards Component
Create reusable stat cards with icons and change indicators.
1234567891011121314151617181920212223242526272829303132interface StatCardProps { label: string; value: string; change: string; trend: "up" | "down"; icon: React.ReactNode; } function StatCard({ label, value, change, trend, icon }: StatCardProps) { return ( <div className="p-6 bg-white dark:bg-slate-900 rounded-2xl border border-slate-200 dark:border-slate-800"> <div className="flex items-center justify-between mb-3"> <span className="text-sm text-slate-500">{label}</span> <div className="p-2 bg-slate-100 dark:bg-slate-800 rounded-lg"> {icon} </div> </div> <div className="flex items-end justify-between"> <span className="text-3xl font-bold text-slate-900 dark:text-white"> {value} </span> <span className={`text-sm font-medium px-2 py-0.5 rounded-full ${ trend === "up" ? "bg-emerald-100 text-emerald-700" : "bg-red-100 text-red-700" }`}> {change} </span> </div> </div> ); }
Implement Revenue Chart
Create an interactive bar chart for revenue visualization.
12345678910111213141516171819202122232425262728293031323334function RevenueChart({ data }: { data: number[] }) { return ( <div className="p-6 bg-white dark:bg-slate-900 rounded-2xl border"> <div className="flex items-center justify-between mb-6"> <h3 className="text-lg font-semibold">Revenue Overview</h3> <select className="px-3 py-1.5 text-sm border rounded-lg"> <option>Last 12 months</option> <option>Last 6 months</option> </select> </div> <div className="h-64 flex items-end gap-3"> {data.map((value, i) => ( <div key={i} className="flex-1"> <div className="bg-purple-100 rounded-t-lg relative overflow-hidden" style={{ height: `${value}%` }} > <div className="absolute bottom-0 inset-x-0 bg-gradient-to-t from-purple-600 to-purple-400 rounded-t-lg" style={{ height: "60%" }} /> </div> </div> ))} </div> <div className="grid grid-cols-12 mt-3 text-xs text-slate-500"> {["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] .map(m => <span key={m} className="text-center">{m}</span>)} </div> </div> ); }
Full Source Code
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271"use client"; import { useState } from "react"; interface StatItem { label: string; value: string; change: string; up: boolean; icon: string; } interface User { name: string; email: string; plan: string; status: string; avatar: string; } interface Activity { user: string; action: string; time: string; type: string; } export default function SaaSDashboard() { const [sidebarOpen, setSidebarOpen] = useState(true); const stats: StatItem[] = [ { label: "Total Revenue", value: "$54,239", change: "+12.5%", up: true, icon: "dollar" }, { label: "Active Users", value: "2,847", change: "+8.2%", up: true, icon: "users" }, { label: "Conversion Rate", value: "3.24%", change: "-0.4%", up: false, icon: "chart" }, { label: "Avg. Session", value: "4m 32s", change: "+15.3%", up: true, icon: "clock" }, ]; const recentUsers: User[] = [ { name: "Sarah Anderson", email: "sarah@example.com", plan: "Pro", status: "active", avatar: "SA" }, { name: "Michael Chen", email: "michael@example.com", plan: "Enterprise", status: "active", avatar: "MC" }, { name: "Emily Johnson", email: "emily@example.com", plan: "Starter", status: "pending", avatar: "EJ" }, ]; const activities: Activity[] = [ { user: "Sarah A.", action: "upgraded to Pro plan", time: "2 min ago", type: "upgrade" }, { user: "New user", action: "signed up for trial", time: "15 min ago", type: "signup" }, { user: "Michael C.", action: "submitted support ticket", time: "1 hour ago", type: "support" }, ]; return ( <div className="min-h-screen bg-slate-50 dark:bg-slate-950 flex"> {/* Collapsible Sidebar */} <aside className={`${sidebarOpen ? "w-64" : "w-0"} bg-white dark:bg-slate-900 border-r transition-all`}> <div className="w-64 h-full flex flex-col"> {/* Logo */} <div className="flex items-center gap-3 px-6 h-16 border-b"> <div className="w-8 h-8 bg-gradient-to-br from-purple-500 to-indigo-600 rounded-lg flex items-center justify-center text-white font-bold"> S </div> <span className="text-lg font-bold">SaaSify</span> </div> {/* Navigation */} <nav className="p-4 space-y-1 flex-1"> {["Dashboard", "Analytics", "Customers", "Products", "Orders", "Messages", "Settings"].map((item) => ( <button key={item} className="w-full flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-medium hover:bg-slate-100" > {item} </button> ))} </nav> {/* User Profile */} <div className="p-4 border-t"> <div className="flex items-center gap-3"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white font-semibold"> JD </div> <div> <p className="text-sm font-medium">John Doe</p> <p className="text-xs text-slate-500">john@saasify.com</p> </div> </div> </div> </div> </aside> {/* Main Content */} <div className="flex-1 flex flex-col"> {/* Header */} <header className="sticky top-0 z-30 h-16 bg-white/80 backdrop-blur-xl border-b"> <div className="flex items-center justify-between h-full px-6"> <button onClick={() => setSidebarOpen(!sidebarOpen)} className="p-2 hover:bg-slate-100 rounded-lg"> {/* Menu Icon */} </button> <div className="flex items-center gap-3"> {/* Search, Notifications, User Avatar */} </div> </div> </header> {/* Dashboard Content */} <main className="flex-1 p-6 space-y-6"> {/* Stats Grid */} <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-5"> {stats.map((stat) => ( <div key={stat.label} className="p-6 bg-white rounded-2xl border shadow-sm"> <div className="flex items-center justify-between mb-3"> <span className="text-sm text-slate-500">{stat.label}</span> {/* Icon */} </div> <div className="flex items-end justify-between"> <span className="text-xl sm:text-2xl font-bold">{stat.value}</span> <span className={`text-sm px-2 py-0.5 rounded-full ${ stat.up ? "bg-emerald-100 text-emerald-700" : "bg-red-100 text-red-700" }`}> {stat.change} </span> </div> </div> ))} </div> {/* Charts and Activity */} <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> {/* Revenue Chart - Edge to Edge with Data Points */} <div className="lg:col-span-2 bg-white rounded-2xl border overflow-hidden flex flex-col"> <div className="p-4 border-b flex items-center justify-between"> <h3 className="text-lg font-semibold">Revenue Overview</h3> <div className="flex items-center gap-4 text-xs text-slate-500"> <span className="flex items-center gap-1.5"> <span className="w-2 h-2 rounded-full bg-purple-500"></span>Revenue </span> </div> </div> <div className="flex-1 min-h-[220px] relative"> {/* Y-axis labels */} <div className="absolute left-3 top-0 bottom-8 flex flex-col justify-between text-xs text-slate-400 z-10"> <span>$50k</span><span>$40k</span><span>$30k</span><span>$20k</span><span>$10k</span><span>$0</span> </div> {/* Grid lines - edge to edge */} <div className="absolute inset-0 bottom-8 flex flex-col justify-between pointer-events-none"> {[0, 1, 2, 3, 4, 5].map((i) => ( <div key={i} className="border-b border-slate-100" /> ))} </div> {/* Line chart SVG - edge to edge */} <svg className="absolute inset-0 w-full" style={{ height: 'calc(100% - 32px)' }} viewBox="0 0 400 200" preserveAspectRatio="none"> <defs> <linearGradient id="lineGradient" x1="0%" y1="0%" x2="0%" y2="100%"> <stop offset="0%" stopColor="rgb(147, 51, 234)" stopOpacity="0.3" /> <stop offset="100%" stopColor="rgb(147, 51, 234)" stopOpacity="0" /> </linearGradient> </defs> <path d="M0,140 L33,120 L66,130 L100,80 L133,100 L166,90 L200,60 L233,85 L266,70 L300,40 L333,75 L366,55 L400,45 L400,200 L0,200 Z" fill="url(#lineGradient)" /> <path d="M0,140 L33,120 L66,130 L100,80 L133,100 L166,90 L200,60 L233,85 L266,70 L300,40 L333,75 L366,55 L400,45" fill="none" stroke="rgb(147, 51, 234)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" vectorEffect="non-scaling-stroke" /> </svg> {/* Data points overlay with hover tooltips */} <div className="absolute inset-0" style={{ height: 'calc(100% - 32px)' }}> {[ { x: 0, y: 70, month: "Jan", value: 15, change: "+12%" }, { x: 8.25, y: 60, month: "Feb", value: 20, change: "+33%" }, { x: 16.5, y: 65, month: "Mar", value: 17.5, change: "-12%" }, { x: 25, y: 40, month: "Apr", value: 30, change: "+71%" }, { x: 33.25, y: 50, month: "May", value: 25, change: "-17%" }, { x: 41.5, y: 45, month: "Jun", value: 27.5, change: "+10%" }, { x: 50, y: 30, month: "Jul", value: 35, change: "+27%" }, { x: 58.25, y: 42.5, month: "Aug", value: 28.75, change: "-18%" }, { x: 66.5, y: 35, month: "Sep", value: 32.5, change: "+13%" }, { x: 75, y: 20, month: "Oct", value: 40, change: "+23%" }, { x: 83.25, y: 37.5, month: "Nov", value: 31.25, change: "-22%" }, { x: 91.5, y: 27.5, month: "Dec", value: 36.25, change: "+16%" } ].map((point, i) => ( <div key={i} className="absolute group z-20 hover:z-50" style={{ left: \`\${point.x}%\`, top: \`\${point.y}%\` }}> <div className="w-3 h-3 bg-white border-2 border-purple-500 rounded-full transform -translate-x-1/2 -translate-y-1/2 group-hover:scale-150 transition-transform cursor-pointer" /> {/* Tooltip */} <div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50"> <div className="bg-slate-900 text-white text-xs rounded-lg px-3 py-2 whitespace-nowrap shadow-lg"> <div className="font-semibold">{point.month} 2024</div> <div className="flex items-center gap-2 mt-1"> <span className="text-purple-400 font-bold">${point.value}k</span> <span className={point.change.startsWith("+") ? "text-emerald-400" : "text-red-400"}>{point.change}</span> </div> </div> <div className="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-slate-900" /> </div> </div> ))} </div> {/* X-axis labels */} <div className="absolute bottom-0 left-0 right-0 h-8 flex justify-between items-center text-xs text-slate-400"> {["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].map((m) => ( <span key={m} className="text-center" style={{ width: '8.33%' }}>{m}</span> ))} </div> </div> </div> {/* Activity Feed */} <div className="p-6 bg-white rounded-2xl border"> <h3 className="text-lg font-semibold mb-4">Recent Activity</h3> <div className="space-y-4"> {activities.map((activity, i) => ( <div key={i} className="flex items-start gap-3"> {/* Activity Icon */} <div> <p className="text-sm"> <span className="font-medium">{activity.user}</span> {activity.action} </p> <p className="text-xs text-slate-500">{activity.time}</p> </div> </div> ))} </div> </div> </div> {/* Users Table */} <div className="bg-white rounded-2xl border overflow-hidden"> <div className="flex items-center justify-between p-6 border-b"> <h3 className="text-lg font-semibold">Recent Customers</h3> <button className="text-sm text-purple-600 font-medium">View all</button> </div> <table className="w-full"> <thead> <tr className="border-b bg-slate-50"> <th className="px-6 py-4 text-left text-xs font-semibold text-slate-500 uppercase">Customer</th> <th className="px-6 py-4 text-left text-xs font-semibold text-slate-500 uppercase">Plan</th> <th className="px-6 py-4 text-left text-xs font-semibold text-slate-500 uppercase">Status</th> <th className="px-6 py-4 text-right text-xs font-semibold text-slate-500 uppercase">Actions</th> </tr> </thead> <tbody> {recentUsers.map((user) => ( <tr key={user.email} className="border-b hover:bg-slate-50"> <td className="px-6 py-4"> <div className="flex items-center gap-3"> <div className="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white font-semibold text-sm"> {user.avatar} </div> <div> <p className="font-medium">{user.name}</p> <p className="text-sm text-slate-500">{user.email}</p> </div> </div> </td> <td className="px-6 py-4"> <span className="px-2.5 py-1 text-xs font-medium rounded-full bg-purple-100 text-purple-700"> {user.plan} </span> </td> <td className="px-6 py-4"> <span className="px-2.5 py-1 text-xs font-medium rounded-full bg-emerald-100 text-emerald-700"> {user.status} </span> </td> <td className="px-6 py-4 text-right"> <button className="p-2 hover:bg-slate-100 rounded-lg">•••</button> </td> </tr> ))} </tbody> </table> </div> </main> </div> </div> ); }
Customization Guide
Brand Colors
Update the color scheme to match your brand identity.
// Replace purple with your brand color
// In tailwind.config.ts:
colors: {
brand: {
50: '#faf5ff',
500: '#a855f7',
600: '#9333ea',
700: '#7e22ce',
}
}
// Then update classes:
// bg-purple-600 → bg-brand-600
// text-purple-500 → text-brand-500Navigation Items
Customize sidebar navigation for your app's needs.
const navItems = [
{ label: "Dashboard", icon: HomeIcon, href: "/dashboard" },
{ label: "Analytics", icon: ChartIcon, href: "/analytics" },
{ label: "Customers", icon: UsersIcon, href: "/customers", badge: 3 },
{ label: "Products", icon: BoxIcon, href: "/products" },
{ label: "Billing", icon: CreditCardIcon, href: "/billing" },
{ label: "Settings", icon: SettingsIcon, href: "/settings" },
];
// Add dividers between sections
const navSections = [
{ title: "Main", items: navItems.slice(0, 4) },
{ title: "Account", items: navItems.slice(4) },
];Stats Configuration
Configure which metrics to display on the dashboard.
const dashboardStats = [
{
label: "Total Revenue",
value: formatCurrency(revenue),
change: calculateChange(revenue, lastRevenue),
icon: <DollarIcon />,
color: "purple",
},
{
label: "Active Users",
value: formatNumber(users),
change: calculateChange(users, lastUsers),
icon: <UsersIcon />,
color: "cyan",
},
// Add more metrics as needed
];Chart Integration
Replace simple charts with a charting library like Recharts.
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
function RevenueChart({ data }) {
return (
<ResponsiveContainer width="100%" height={256}>
<BarChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Bar
dataKey="revenue"
fill="#a855f7"
radius={[4, 4, 0, 0]}
/>
</BarChart>
</ResponsiveContainer>
);
}Ready to Build Your Dashboard?
Copy this template, customize it to your brand, and start tracking your metrics. All the components you need are included.