Tutorial

Dashboard Layout Recipe with AI

Create professional admin dashboard layouts using FramingUI templates with AI assistance for rapid development.

FramingUI Team7 min read

Why Dashboards Are Hard

Building a dashboard from scratch involves:

  1. Layout structure — Sidebar, header, main content area
  2. Responsive design — Mobile, tablet, desktop breakpoints
  3. Component composition — Stats cards, tables, charts
  4. Navigation state — Active links, collapsible menus
  5. 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>
  );
}

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

  1. Add dark mode toggle: Use FramingUI theme switching
  2. Implement data fetching: React Query or SWR
  3. Add filtering/search: Table with search input
  4. 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.

Ready to build with FramingUI?

Join the beta and get early access to agentic design systems that adapt to your needs.

Join Beta
Share

Related Posts