Why Dashboards Are Hard
Building a dashboard from scratch involves:
- Layout structure — Sidebar, header, main content area
- Responsive design — Mobile, tablet, desktop breakpoints
- Component composition — Stats cards, tables, charts
- Navigation state — Active links, collapsible menus
- Consistent styling — Colors, spacing, typography
With FramingUI + AI, you can generate a complete dashboard in minutes, not hours.
Dashboard Anatomy
A typical admin dashboard has:
┌─────────────────────────────────────────┐
│ Header (navigation, user menu) │
├──────────┬──────────────────────────────┤
│ │ │
│ Sidebar │ Main Content Area │
│ │ - Stats Cards │
│ (nav) │ - Charts │
│ │ - Data Tables │
│ │ │
└──────────┴──────────────────────────────┘
Let's build this step-by-step with FramingUI.
Step 1: Basic Layout Structure
import { Card, Separator } from '@framingui/ui';
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen">
{/* Sidebar */}
<aside className="w-64 border-r border-[var(--tekton-border-default)] bg-[var(--tekton-bg-card)]">
<div className="p-[var(--tekton-spacing-6)]">
<h2 className="text-lg font-bold">MyApp</h2>
</div>
<Separator />
{/* Navigation will go here */}
</aside>
{/* Main content */}
<div className="flex-1 flex flex-col">
{/* Header */}
<header className="h-16 border-b border-[var(--tekton-border-default)] bg-[var(--tekton-bg-card)] flex items-center px-[var(--tekton-spacing-6)]">
<h1 className="text-xl font-semibold">Dashboard</h1>
</header>
{/* Content */}
<main className="flex-1 overflow-auto p-[var(--tekton-spacing-6)] bg-[var(--tekton-bg-background)]">
{children}
</main>
</div>
</div>
);
}
Key Features
✅ Fixed sidebar: w-64 (256px width)
✅ Scrollable content: overflow-auto on main
✅ Design tokens: All borders and spacing use tokens
Step 2: Sidebar Navigation
'use client';
import { useState } from 'react';
import { Home, Users, BarChart3, Settings, ChevronDown } from 'lucide-react';
import { Button, Separator } from '@framingui/ui';
import { cn } from '@framingui/ui/lib/utils';
const navItems = [
{ icon: Home, label: 'Dashboard', href: '/dashboard' },
{ icon: Users, label: 'Users', href: '/users' },
{ icon: BarChart3, label: 'Analytics', href: '/analytics' },
{ icon: Settings, label: 'Settings', href: '/settings' },
];
export function DashboardSidebar() {
const [activeItem, setActiveItem] = useState('/dashboard');
return (
<aside className="w-64 border-r border-[var(--tekton-border-default)] bg-[var(--tekton-bg-card)] flex flex-col h-screen">
{/* Logo */}
<div className="p-[var(--tekton-spacing-6)]">
<h2 className="text-lg font-bold">MyApp Admin</h2>
</div>
<Separator />
{/* Navigation */}
<nav className="flex-1 p-[var(--tekton-spacing-4)] space-y-[var(--tekton-spacing-2)]">
{navItems.map((item) => (
<Button
key={item.href}
variant={activeItem === item.href ? 'default' : 'ghost'}
className={cn(
'w-full justify-start',
activeItem === item.href && 'bg-[var(--tekton-bg-primary)] text-[var(--tekton-bg-primary-foreground)]'
)}
onClick={() => setActiveItem(item.href)}
>
<item.icon className="mr-[var(--tekton-spacing-2)] h-4 w-4" />
{item.label}
</Button>
))}
</nav>
<Separator />
{/* User section */}
<div className="p-[var(--tekton-spacing-4)]">
<Button variant="ghost" className="w-full justify-between">
<div className="flex items-center">
<div className="w-8 h-8 rounded-full bg-[var(--tekton-bg-primary)] flex items-center justify-center text-sm font-bold text-[var(--tekton-bg-primary-foreground)] mr-[var(--tekton-spacing-3)]">
JD
</div>
<div className="text-left">
<p className="text-sm font-medium">John Doe</p>
<p className="text-xs text-[var(--tekton-bg-muted-foreground)]">Admin</p>
</div>
</div>
<ChevronDown className="h-4 w-4" />
</Button>
</div>
</aside>
);
}
Navigation Features
✅ Active state highlighting: Uses variant="default" for active item
✅ Icon support: Lucide React icons
✅ User profile section: At bottom with avatar
Step 3: Stats Cards Grid
import { Card, CardContent } from '@framingui/ui';
import { Users, DollarSign, ShoppingCart, TrendingUp } from 'lucide-react';
const stats = [
{
title: 'Total Revenue',
value: '$45,231',
change: '+20.1%',
icon: DollarSign,
trend: 'up' as const,
},
{
title: 'Total Users',
value: '2,350',
change: '+12.5%',
icon: Users,
trend: 'up' as const,
},
{
title: 'Total Orders',
value: '1,234',
change: '-4.3%',
icon: ShoppingCart,
trend: 'down' as const,
},
{
title: 'Conversion Rate',
value: '3.24%',
change: '+0.5%',
icon: TrendingUp,
trend: 'up' as const,
},
];
export function StatsGrid() {
return (
<div className="grid gap-[var(--tekton-spacing-4)] md:grid-cols-2 lg:grid-cols-4">
{stats.map((stat) => (
<Card key={stat.title}>
<CardContent className="p-[var(--tekton-spacing-6)]">
<div className="flex items-center justify-between space-x-[var(--tekton-spacing-4)]">
<div className="space-y-[var(--tekton-spacing-2)]">
<p className="text-sm font-medium text-[var(--tekton-bg-muted-foreground)]">
{stat.title}
</p>
<div className="flex items-baseline space-x-[var(--tekton-spacing-2)]">
<p className="text-2xl font-bold">{stat.value}</p>
<span
className={cn(
'text-xs font-medium',
stat.trend === 'up'
? 'text-[var(--tekton-bg-primary)]'
: 'text-[var(--tekton-bg-destructive)]'
)}
>
{stat.change}
</span>
</div>
</div>
<div className="p-[var(--tekton-spacing-3)] bg-[var(--tekton-bg-primary)]/10 rounded-[var(--tekton-radius-lg)]">
<stat.icon className="h-5 w-5 text-[var(--tekton-bg-primary)]" />
</div>
</div>
</CardContent>
</Card>
))}
</div>
);
}
Stats Card Features
✅ Responsive grid: 1 column mobile → 2 tablet → 4 desktop ✅ Trend indicators: Green for up, red for down ✅ Icon badges: Colored background with opacity
Step 4: Data Table
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Card,
CardHeader,
CardTitle,
CardContent,
Badge,
Button,
} from '@framingui/ui';
import { MoreHorizontal } from 'lucide-react';
const recentOrders = [
{ id: '#3210', customer: 'Alice Johnson', amount: '$250.00', status: 'completed', date: '2026-02-27' },
{ id: '#3211', customer: 'Bob Smith', amount: '$120.50', status: 'pending', date: '2026-02-27' },
{ id: '#3212', customer: 'Carol White', amount: '$89.99', status: 'completed', date: '2026-02-26' },
{ id: '#3213', customer: 'David Brown', amount: '$340.00', status: 'processing', date: '2026-02-26' },
];
export function RecentOrdersTable() {
return (
<Card>
<CardHeader>
<CardTitle>Recent Orders</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Order ID</TableHead>
<TableHead>Customer</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date</TableHead>
<TableHead className="w-[50px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{recentOrders.map((order) => (
<TableRow key={order.id}>
<TableCell className="font-medium">{order.id}</TableCell>
<TableCell>{order.customer}</TableCell>
<TableCell>{order.amount}</TableCell>
<TableCell>
<Badge
variant={
order.status === 'completed'
? 'default'
: order.status === 'pending'
? 'secondary'
: 'outline'
}
>
{order.status}
</Badge>
</TableCell>
<TableCell className="text-[var(--tekton-bg-muted-foreground)]">
{order.date}
</TableCell>
<TableCell>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
);
}
Table Features
✅ Status badges: Color-coded order statuses ✅ Action buttons: Icon button for row actions ✅ Card wrapper: Table inside Card for consistent styling
Step 5: Complete Dashboard Page
import { DashboardLayout } from './dashboard-layout';
import { DashboardSidebar } from './dashboard-sidebar';
import { StatsGrid } from './stats-grid';
import { RecentOrdersTable } from './recent-orders-table';
export default function DashboardPage() {
return (
<div className="flex h-screen">
<DashboardSidebar />
<div className="flex-1 flex flex-col">
{/* Header */}
<header className="h-16 border-b border-[var(--tekton-border-default)] bg-[var(--tekton-bg-card)] flex items-center justify-between px-[var(--tekton-spacing-6)]">
<h1 className="text-xl font-semibold">Dashboard Overview</h1>
<Button variant="default">Download Report</Button>
</header>
{/* Main content */}
<main className="flex-1 overflow-auto p-[var(--tekton-spacing-6)] bg-[var(--tekton-bg-background)]">
<div className="space-y-[var(--tekton-spacing-6)]">
<StatsGrid />
<RecentOrdersTable />
</div>
</main>
</div>
</div>
);
}
Responsive Enhancements
Mobile-First Sidebar
'use client';
import { useState } from 'react';
import { Menu, X } from 'lucide-react';
import { Button, Sheet, SheetContent, SheetTrigger } from '@framingui/ui';
export function MobileSidebar({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<>
{/* Mobile trigger */}
<div className="lg:hidden">
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button variant="ghost" size="icon">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="left" className="p-0 w-64">
{children}
</SheetContent>
</Sheet>
</div>
{/* Desktop sidebar */}
<div className="hidden lg:block">{children}</div>
</>
);
}
AI Prompting Tips
Generate Stats Cards
Prompt for Claude/Cursor:
Create a stats grid component using FramingUI Card showing:
- Total Users (12,345)
- Active Sessions (1,234)
- Revenue ($54,321)
- Conversion Rate (3.2%)
Use var(--tekton-*) tokens for all styling.
Generate Data Table
Prompt:
Create a users table using FramingUI Table component with columns:
- Name, Email, Role, Status (badge), Actions (button)
Use design tokens for spacing and colors.
Generate Complete Dashboard
Prompt:
Create a complete admin dashboard using FramingUI with:
- Sidebar navigation (Dashboard, Users, Analytics, Settings)
- Header with title and action button
- 4 stats cards in responsive grid
- Recent orders table
- All styling using var(--tekton-*) tokens
Advanced: Chart Integration
import { Card, CardHeader, CardTitle, CardContent } from '@framingui/ui';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
const chartData = [
{ month: 'Jan', revenue: 4000 },
{ month: 'Feb', revenue: 3000 },
{ month: 'Mar', revenue: 5000 },
{ month: 'Apr', revenue: 4500 },
{ month: 'May', revenue: 6000 },
{ month: 'Jun', revenue: 5500 },
];
export function RevenueChart() {
return (
<Card>
<CardHeader>
<CardTitle>Revenue Overview</CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="revenue"
stroke="var(--tekton-bg-primary)"
strokeWidth={2}
/>
</LineChart>
</ResponsiveContainer>
</CardContent>
</Card>
);
}
✅ Uses design token: stroke="var(--tekton-bg-primary)"
Next Steps
- Add dark mode toggle: Use FramingUI theme switching
- Implement data fetching: React Query or SWR
- Add filtering/search: Table with search input
- Build detail pages: User profile, order details
Resources
With FramingUI + AI, you can build production-ready dashboards in minutes while maintaining perfect design consistency across every component.