HRMS Design Guide
iOS HIG-aligned HRMS with colorful widgets and AI assistant
HRMS Design Guide
Build a polished, production-ready HRMS application based on the HRMSElegance prototype with a clean aesthetic and vibrant, colorful widgets. This should look like a shipped product, not a wireframe.
Looking for a ready-to-use prompt? See the AI Builder Prompt for a copy-paste prompt for Lovable, Replit, and similar platforms.
Design Philosophy: HRMSElegance Prototype
| Element | Design Principle | Implementation |
|---|---|---|
| Background | Apple-style grouped | #F2F2F7 (gray-100) main, white cards |
| Sidebar | Theme-aware | White (light) / #1C1C1E (dark mode) |
| Stats Cards | Colorful gradients | Blue, amber→orange, violet→purple, emerald→green |
| Content Cards | Minimal with depth | White + soft shadows + dark mode variant |
| Buttons | Primary blue | #008CF0, pill-shaped with scale effects |
| AI Button | Violet→Fuchsia | Violet→purple→fuchsia gradient with glow |
| Typography | Semantic colors | card-foreground, muted-foreground |
| Transitions | Spring-based | Framer Motion with spring physics |
The Key Insight
PLAIN WHITE CARDS = Wireframe (BAD)
GRADIENT STATS + WHITE CONTENT + HOVER EFFECTS = Shipped Product (GOOD)Visual Balance
┌──────────────────────────────────────────────────────────────────────┐
│ ┌────────────┐ ┌────────────────────────────────────────────────┐ │
│ │ │ │ STICKY TOPBAR (backdrop-blur) 🔍 👤 │ │
│ │ THEME- │ └────────────────────────────────────────────────┘ │
│ │ AWARE │ │
│ │ SIDEBAR │ ┌────────────────────────────────────────────────┐ │
│ │ │ │ GRAY-100 BACKGROUND (#F2F2F7) │ │
│ │ ┌──────┐ │ │ │ │
│ │ │ LOGO │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ └──────┘ │ │ │ BLUE │ │ AMBER→ │ │ VIOLET→ │ │ │
│ │ │ │ │ GRADIENT│ │ ORANGE │ │ PURPLE │ ← HOVER │ │
│ │ 🏠 Active │ │ │ 156 │ │ 8 │ │ 12 │ LIFT │ │
│ │ (blue+ │ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ shadow) │ │ │ │
│ │ │ │ ┌───────────────────────────────────────┐ │ │
│ │ 👥 Hover │ │ │ WHITE CARD + SOFT SHADOW │ │ │
│ │ 📅 etc │ │ │ dark:shadow-black/40 │ ← DEPTH
│ │ │ │ └───────────────────────────────────────┘ │ │
│ │ ───────── │ │ ┌────┐ │ │
│ │ 👤 User │ │ │ AI │ │ │
│ └────────────┘ │ └────┘ │ │
│ Light/Dark └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘Visual Reference
The design follows the Screver pattern:
DESKTOP LAYOUT (1024px+):
┌──────────────────────────────────────────────────────────────────────┐
│ ┌────────────┐ │
│ │ │ ┌────────────────────────────────────────────────┐ │
│ │ LOGO │ │ Page Title 🔍 👤 │ │
│ │ │ └────────────────────────────────────────────────┘ │
│ ├────────────┤ │
│ │ │ ┌──────────────────────────────────────────────────┐│
│ │ + New │ │ ││
│ │ [purple→ │ │ WHITE CONTENT AREA ││
│ │ pink] │ │ ││
│ ├────────────┤ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││
│ │ 🏠 Home │ │ │ Card │ │ Card │ │ Card │ ││
│ │ 👥 People │ │ │ │ │ │ │ │ ││
│ │ 📅 Time Off│ │ └─────────┘ └─────────┘ └─────────┘ ││
│ │ 📄 Docs │ │ ││
│ │ ⚙️ Settings│ │ ┌────┐ ││
│ │ │ │ │ AI │ ││
│ ├────────────┤ │ └────┘ ││
│ │ 👤 User │ │ ││
│ │ │ └──────────────────────────────────────────────────┘│
│ └────────────┘ │
│ iOS DARK MODE │
│ (neutral-900) │
└──────────────────────────────────────────────────────────────────────┘
COLLAPSED SIDEBAR:
┌────┐
│ ≡ │ ← Toggle button
├────┤
│ + │
├────┤
│ 🏠 │
│ 👥 │
│ 📅 │
│ 📄 │
│ ⚙️ │
├────┤
│ 👤 │
└────┘
MOBILE LAYOUT (<768px):
┌─────────────────────────────────────┐
│ ☰ HRMS 🔍 👤 │ ← Header with hamburger
├─────────────────────────────────────┤
│ │
│ WHITE CONTENT AREA │
│ │
│ ┌─────────────────────────────┐ │
│ │ Card │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Card │ │
│ └─────────────────────────────┘ │
│ │
│ ┌────┐ │
│ │ AI │ │
│ └────┘ │
├─────────────────────────────────────┤
│ 🏠 👥 📅 📄 ⚙️ │ ← Bottom nav (optional)
│ Home People Time Docs More │
└─────────────────────────────────────┘Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| React | 19.x | UI library with TypeScript |
| Tailwind CSS | 3.4 | Utility-first styling |
| shadcn/ui | Latest | Accessible components |
| React Router | v6 | Navigation |
| Framer Motion | Latest | Animations |
| Lucide React | Latest | Icons |
| Recharts | Latest | Charts |
| date-fns | Latest | Dates |
Color System (HRMSElegance Prototype)
Both exact hex values and Tailwind class equivalents are provided.
Core Palette
const colors = {
// ============================================
// PRIMARY COLORS (HSL → Hex → Tailwind)
// ============================================
primary: {
main: '#008CF0', // HSL: 205 100% 47.1% | Tailwind: blue-500
foreground: '#FFFFFF', // White
},
secondary: {
main: '#383B53', // HSL: 233 19.4% 27.3% | Tailwind: slate-700
foreground: '#FFFFFF', // White
},
// ============================================
// BACKGROUND COLORS
// ============================================
background: {
primary: '#F2F2F7', // HSL: 240 14% 96% | Tailwind: gray-100 - Main app bg
card: '#FFFFFF', // White | Tailwind: white - Cards, modals
muted: '#F4F4F5', // HSL: 240 5% 96% | Tailwind: gray-100
},
// ============================================
// LABEL/TEXT COLORS
// ============================================
label: {
primary: '#000000', // Tailwind: gray-900 - Titles, headlines
secondary: '#8E8E93', // HSL: 240 4% 46% | Tailwind: gray-500 - Captions
muted: '#8E8E93', // Tailwind: gray-500 - Muted foreground
},
// ============================================
// SIDEBAR - Theme-Aware (Light/Dark)
// ============================================
sidebar: {
light: {
bg: '#FFFFFF', // bg-card
active: '#008CF0', // bg-primary
activeShadow: 'shadow-md shadow-primary/20',
hover: '#383B53', // bg-secondary
text: '#000000', // text-card-foreground
textMuted: '#8E8E93', // text-muted-foreground
border: 'border-border',
shadow: 'shadow-2xl shadow-black/5',
},
dark: {
bg: '#1C1C1E', // iOS dark bg
active: '#008CF0', // Same primary blue
hover: '#2C2C2E', // iOS secondary dark
text: '#FAFAFA', // Off-white
textMuted: '#A3A3A3', // text-neutral-400
},
},
// ============================================
// STATS CARDS - Colorful Gradients with Hover
// ============================================
stats: {
employees: {
gradient: 'from-blue-500 to-blue-600',
iconBg: 'bg-white/20 backdrop-blur-sm',
muted: 'text-white/80',
hover: 'hover:shadow-xl hover:-translate-y-1',
},
onLeave: {
gradient: 'from-amber-400 to-orange-500', // Amber→Orange
iconBg: 'bg-white/20 backdrop-blur-sm',
muted: 'text-white/80',
hover: 'hover:shadow-xl hover:-translate-y-1',
},
pending: {
gradient: 'from-violet-500 to-purple-600', // Violet→Purple
iconBg: 'bg-white/20 backdrop-blur-sm',
muted: 'text-white/80',
hover: 'hover:shadow-xl hover:-translate-y-1',
},
positions: {
gradient: 'from-emerald-400 to-green-500', // Emerald→Green
iconBg: 'bg-white/20 backdrop-blur-sm',
muted: 'text-white/80',
hover: 'hover:shadow-xl hover:-translate-y-1',
},
upcomingEvent: {
gradient: 'from-indigo-900 to-indigo-950', // Indigo (dark)
iconBg: 'bg-white/20 backdrop-blur-sm',
muted: 'text-white/80',
hover: 'hover:shadow-xl hover:-translate-y-1',
},
},
// ============================================
// CONTENT CARDS - With Dark Mode Support
// ============================================
card: {
bg: 'bg-card', // white / dark gray
shadowLight: 'shadow-lg shadow-gray-200/50',
shadowDark: 'dark:shadow-black/40',
radius: 'rounded-2xl',
padding: 'p-6',
border: 'border-none',
},
// ============================================
// STATUS/FUNCTIONAL COLORS
// ============================================
status: {
success: '#52C41A', // HSL: 96 74.6% 43.5% | Tailwind: green-500
warning: '#FAAD14', // HSL: 40 95.8% 52.9% | Tailwind: amber-500
error: '#FF4D4F', // HSL: 359 100% 65.1% | Tailwind: red-500
},
// ============================================
// BUTTONS - With Scale Effects
// ============================================
button: {
primary: 'bg-primary text-white shadow-lg shadow-primary/25',
primaryHover: 'hover:shadow-primary/35 hover:scale-[1.02]',
primaryActive: 'active:scale-95',
secondary: 'bg-secondary text-secondary-foreground',
destructive: 'bg-destructive text-destructive-foreground',
ghost: 'border border-transparent hover:bg-secondary',
radius: 'rounded-full',
},
// ============================================
// AI BUTTON - Violet→Purple→Fuchsia
// ============================================
ai: {
gradient: 'from-violet-500 via-purple-500 to-fuchsia-500',
shadow: 'shadow-xl shadow-violet-500/30',
shadowHover: 'shadow-2xl shadow-violet-500/40',
scale: 'hover:scale-105 active:scale-95',
},
// ============================================
// CHARTS (HRMSElegance)
// ============================================
charts: {
chart1: '#008CF0', // Blue
chart2: '#52C41A', // Green
chart3: '#FAAD14', // Amber
chart4: '#FF4D4F', // Red
chart5: '#5CBDBD', // Cyan/Teal
},
};
// ============================================
// ANIMATION SYSTEM (Framer Motion + Tailwind)
// ============================================
const animations = {
// Tailwind transition classes
transitions: {
default: 'transition-all duration-200',
hover: 'transition-all duration-300',
colors: 'transition-colors',
transform: 'transition-transform',
shadow: 'transition-shadow',
},
// Framer Motion spring configs
spring: {
default: { type: 'spring', damping: 25, stiffness: 300 },
bouncy: { type: 'spring', damping: 15, stiffness: 400 },
},
// Framer Motion variants
variants: {
buttonEntrance: {
initial: { scale: 0.8, opacity: 0 },
animate: { scale: 1, opacity: 1 },
exit: { scale: 0.8, opacity: 0 },
},
modalEntrance: {
initial: { opacity: 0, y: 20, scale: 0.95 },
animate: { opacity: 1, y: 0, scale: 1 },
exit: { opacity: 0, y: 20, scale: 0.95 },
},
cardEntrance: {
initial: { opacity: 0, scale: 0.8 },
animate: { opacity: 1, scale: 1 },
},
},
guidelines: [
'Use Framer Motion for complex animations',
'Use Tailwind transitions for simple hover effects',
'Support prefers-reduced-motion media query',
'Use spring physics for natural feel',
],
};Tailwind Class Presets (Copy-Paste Ready)
const theme = {
// Stats cards (with hover lift effect)
statsCard: 'rounded-2xl p-6 text-white shadow-lg transition-all hover:shadow-xl hover:-translate-y-1 duration-300',
statsBlue: 'bg-gradient-to-br from-blue-500 to-blue-600',
statsAmber: 'bg-gradient-to-br from-amber-400 to-orange-500',
statsViolet: 'bg-gradient-to-br from-violet-500 to-purple-600',
statsEmerald: 'bg-gradient-to-br from-emerald-400 to-green-500',
statsIcon: 'w-12 h-12 bg-white/20 rounded-2xl flex items-center justify-center backdrop-blur-sm',
// Content cards (with dark mode shadow)
card: 'bg-card text-card-foreground rounded-2xl shadow-lg shadow-gray-200/50 dark:shadow-black/40 p-6 border-none',
// Buttons (with scale effects)
btnPrimary: 'bg-primary text-white font-medium px-3 py-2 rounded-full shadow-lg shadow-primary/25 hover:shadow-primary/35 hover:scale-[1.02] active:scale-95 transition-all',
btnSecondary: 'bg-secondary text-secondary-foreground font-medium px-6 py-2.5 rounded-full transition-all duration-200',
btnDestructive: 'bg-destructive text-destructive-foreground font-medium px-6 py-2.5 rounded-full transition-all duration-200',
btnGhost: 'border border-transparent hover:bg-secondary font-medium px-6 py-2.5 rounded-full transition-all duration-200',
// AI Button (Violet→Purple→Fuchsia)
aiButton: 'fixed z-50 bottom-6 right-6 md:bottom-8 md:right-8 w-14 h-14 rounded-full bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 text-white shadow-xl shadow-violet-500/30 hover:shadow-2xl hover:shadow-violet-500/40 hover:scale-105 active:scale-95 transition-all duration-200',
// Text (semantic colors)
heading: 'text-card-foreground font-semibold',
body: 'text-muted-foreground',
muted: 'text-muted-foreground',
// Sidebar (Theme-aware)
sidebar: 'bg-card text-card-foreground w-64 border-r border-border shadow-2xl shadow-black/5',
sidebarItem: 'flex items-center gap-3 px-3 py-2 rounded-lg text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors',
sidebarItemActive: 'bg-primary text-white shadow-md shadow-primary/20',
// TopBar (sticky with blur)
topBar: 'sticky top-0 z-20 w-full bg-background/80 backdrop-blur-md border-b border-border px-4 sm:px-6 h-16',
};Design Rules (Critical!)
DO (Required)
| Element | Correct Implementation |
|---|---|
| Stats cards | bg-gradient-to-br from-blue-500 to-blue-600 text-white rounded-2xl shadow-lg p-6 |
| Content cards | bg-white rounded-2xl shadow-lg shadow-gray-200/50 p-6 (NO borders!) |
| Buttons | bg-blue-500 text-white rounded-full px-6 py-2.5 |
| Shadows | shadow-lg or shadow-xl with subtle gray tint |
| Corners | rounded-2xl (16px) for cards |
| Spacing | p-6 inside cards, gap-6 between cards |
DO NOT (Common Mistakes)
| Mistake | Why It's Wrong |
|---|---|
| Plain white stats cards | Looks like a wireframe |
border border-gray-200 | Use shadows, not borders |
rounded-lg (8px) | Too small, use rounded-2xl |
| Gray everywhere | Use vibrant colors for key elements |
Small padding (p-4) | Too cramped, use p-6 |
Quick Visual Test
Ask yourself: "Does this look like Apple.com or like a Figma wireframe?"
- If it looks like Apple → Good
- If it looks like a wireframe → Add gradients and shadows
Project Structure
src/
├── components/
│ ├── layout/
│ │ ├── AppShell.tsx # Main layout wrapper
│ │ ├── Sidebar.tsx # Collapsible sidebar
│ │ ├── SidebarItem.tsx # Navigation item
│ │ ├── Header.tsx # Top header (mobile)
│ │ ├── BottomNav.tsx # Mobile bottom nav
│ │ └── MobileDrawer.tsx # Hamburger menu drawer
│ ├── ai/
│ │ ├── AIProvider.tsx # AI context
│ │ ├── AIFloatingButton.tsx # Floating button
│ │ ├── AIChatPanel.tsx # Chat panel
│ │ ├── ChatMessage.tsx # Message bubble
│ │ └── ActionCard.tsx # Interactive cards
│ ├── dashboard/
│ │ ├── StatCard.tsx # Single stat
│ │ ├── StatsGrid.tsx # Stats layout
│ │ ├── RecentActivity.tsx # Activity feed
│ │ └── QuickActions.tsx # Quick action buttons
│ ├── employees/
│ │ ├── EmployeeCard.tsx # Employee card
│ │ ├── EmployeeTable.tsx # Table view
│ │ ├── EmployeeFilters.tsx # Search & filters
│ │ └── EmployeeDetail.tsx # Detail panel
│ ├── time-off/
│ │ ├── BalanceCard.tsx # Balance display
│ │ ├── RequestForm.tsx # Request form
│ │ ├── RequestList.tsx # Request list
│ │ └── TeamCalendar.tsx # Calendar view
│ └── ui/ # shadcn components
├── pages/
│ ├── Dashboard.tsx
│ ├── Employees.tsx
│ ├── TimeOff.tsx
│ ├── Documents.tsx
│ └── Settings.tsx
├── hooks/
│ ├── useAI.ts
│ ├── useSidebar.ts
│ └── useMediaQuery.ts
├── lib/
│ ├── mock-data.ts
│ └── utils.ts
└── App.tsxLayout Components
App Shell (Main Layout)
// components/layout/AppShell.tsx
const AppShell = ({ children }: { children: React.ReactNode }) => {
const [sidebarOpen, setSidebarOpen] = useState(true);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div className="min-h-screen bg-gray-50">
{/* Desktop Sidebar */}
{!isMobile && (
<Sidebar
collapsed={!sidebarOpen}
onToggle={() => setSidebarOpen(!sidebarOpen)}
/>
)}
{/* Mobile Header */}
{isMobile && (
<Header onMenuClick={() => setMobileMenuOpen(true)} />
)}
{/* Mobile Drawer */}
<MobileDrawer
open={mobileMenuOpen}
onClose={() => setMobileMenuOpen(false)}
/>
{/* Main Content */}
<main className={cn(
"min-h-screen transition-all duration-300",
!isMobile && (sidebarOpen ? "ml-64" : "ml-20"),
isMobile && "pt-16 pb-20"
)}>
<div className="p-6 bg-white min-h-screen">
{children}
</div>
</main>
{/* Mobile Bottom Nav */}
{isMobile && <BottomNav />}
{/* AI Floating Button - Always visible */}
<AIFloatingButton />
{/* AI Chat Panel */}
<AIChatPanel />
</div>
);
};Sidebar (Desktop)
// components/layout/Sidebar.tsx
const navItems = [
{ icon: Home, label: 'Dashboard', path: '/' },
{ icon: Users, label: 'Employees', path: '/employees' },
{ icon: Calendar, label: 'Time Off', path: '/time-off' },
{ icon: FileText, label: 'Documents', path: '/documents' },
{ icon: Settings, label: 'Settings', path: '/settings' },
];
const Sidebar = ({ collapsed, onToggle }: SidebarProps) => {
const location = useLocation();
return (
<aside className={cn(
"fixed left-0 top-0 h-screen z-40",
"bg-neutral-900 text-white",
"flex flex-col",
"transition-all duration-300",
collapsed ? "w-20" : "w-64"
)}>
{/* Logo & Toggle */}
<div className="h-16 flex items-center justify-between px-4 border-b border-indigo-700">
{!collapsed && (
<span className="text-xl font-bold">HRMS</span>
)}
<button
onClick={onToggle}
className="p-2 rounded-lg hover:bg-indigo-800 transition-colors"
>
{collapsed ? <ChevronRight size={20} /> : <ChevronLeft size={20} />}
</button>
</div>
{/* Primary Action */}
<div className="p-4">
<button className={cn(
"w-full flex items-center justify-center gap-2",
"bg-gradient-to-r from-purple-500 to-pink-500",
"hover:from-purple-600 hover:to-pink-600",
"text-white font-medium shadow-lg shadow-purple-500/30",
"rounded-2xl transition-all",
collapsed ? "p-3" : "px-4 py-3"
)}>
<Plus size={20} />
{!collapsed && <span>New Employee</span>}
</button>
</div>
{/* Navigation */}
<nav className="flex-1 px-3 py-2 space-y-1">
{navItems.map(item => (
<Link
key={item.path}
to={item.path}
className={cn(
"flex items-center gap-3 px-3 py-3 rounded-lg",
"transition-colors",
location.pathname === item.path
? "bg-indigo-600 text-white"
: "text-indigo-200 hover:bg-indigo-800 hover:text-white"
)}
>
<item.icon size={20} />
{!collapsed && <span>{item.label}</span>}
</Link>
))}
</nav>
{/* User Profile */}
<div className="p-4 border-t border-indigo-700">
<div className={cn(
"flex items-center gap-3",
collapsed && "justify-center"
)}>
<div className="w-10 h-10 rounded-full bg-indigo-600 flex items-center justify-center">
<span className="text-sm font-medium">SC</span>
</div>
{!collapsed && (
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">Sarah Chen</p>
<p className="text-xs text-indigo-300 truncate">HR Manager</p>
</div>
)}
</div>
</div>
</aside>
);
};Mobile Header
// components/layout/Header.tsx
const Header = ({ onMenuClick }: { onMenuClick: () => void }) => {
return (
<header className="fixed top-0 left-0 right-0 z-30 h-16 bg-white shadow-md shadow-gray-200/30">
<div className="h-full px-4 flex items-center justify-between">
{/* Hamburger */}
<button
onClick={onMenuClick}
className="p-2 -ml-2 rounded-lg hover:bg-gray-100"
>
<Menu size={24} className="text-gray-700" />
</button>
{/* Logo */}
<span className="text-lg font-bold text-gray-900">HRMS</span>
{/* Actions */}
<div className="flex items-center gap-2">
<button className="p-2 rounded-lg hover:bg-gray-100">
<Search size={20} className="text-gray-500" />
</button>
<button className="p-2 rounded-lg hover:bg-gray-100">
<Bell size={20} className="text-gray-500" />
</button>
</div>
</div>
</header>
);
};Mobile Bottom Navigation
// components/layout/BottomNav.tsx
const bottomNavItems = [
{ icon: Home, label: 'Home', path: '/' },
{ icon: Users, label: 'People', path: '/employees' },
{ icon: Calendar, label: 'Time Off', path: '/time-off' },
{ icon: FileText, label: 'Docs', path: '/documents' },
{ icon: MoreHorizontal, label: 'More', path: '/more' },
];
const BottomNav = () => {
const location = useLocation();
return (
<nav className="fixed bottom-0 left-0 right-0 z-30 bg-white shadow-lg shadow-gray-200/50 safe-area-pb">
<div className="flex justify-around items-center h-16">
{bottomNavItems.map(item => (
<Link
key={item.path}
to={item.path}
className={cn(
"flex flex-col items-center gap-1 px-3 py-2 min-w-[64px]",
location.pathname === item.path
? "text-violet-600"
: "text-gray-500"
)}
>
<item.icon size={20} />
<span className="text-xs font-medium">{item.label}</span>
</Link>
))}
</div>
</nav>
);
};Mobile Drawer (Hamburger Menu)
// components/layout/MobileDrawer.tsx
const MobileDrawer = ({ open, onClose }: { open: boolean; onClose: () => void }) => {
return (
<AnimatePresence>
{open && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 bg-black/50 z-40"
/>
{/* Drawer */}
<motion.div
initial={{ x: '-100%' }}
animate={{ x: 0 }}
exit={{ x: '-100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="fixed left-0 top-0 bottom-0 w-72 bg-indigo-900 z-50"
>
{/* Header */}
<div className="h-16 flex items-center justify-between px-4 border-b border-indigo-700">
<span className="text-xl font-bold text-white">HRMS</span>
<button
onClick={onClose}
className="p-2 rounded-lg hover:bg-indigo-800 text-white"
>
<X size={20} />
</button>
</div>
{/* Navigation */}
<nav className="p-4 space-y-1">
{navItems.map(item => (
<Link
key={item.path}
to={item.path}
onClick={onClose}
className="flex items-center gap-3 px-4 py-3 rounded-lg text-indigo-200 hover:bg-indigo-800 hover:text-white transition-colors"
>
<item.icon size={20} />
<span>{item.label}</span>
</Link>
))}
</nav>
{/* User Profile */}
<div className="absolute bottom-0 left-0 right-0 p-4 border-t border-indigo-700">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-indigo-600 flex items-center justify-center text-white">
SC
</div>
<div>
<p className="text-sm font-medium text-white">Sarah Chen</p>
<p className="text-xs text-indigo-300">HR Manager</p>
</div>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
};Dashboard Page
// pages/Dashboard.tsx
const Dashboard = () => {
return (
<div className="space-y-6">
{/* Page Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
<p className="text-gray-500 mt-1">Welcome back, Sarah</p>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard
icon={Users}
label="Total Employees"
value="156"
trend="+5%"
trendUp
/>
<StatCard
icon={Calendar}
label="On Leave Today"
value="8"
color="amber"
/>
<StatCard
icon={Clock}
label="Pending Approvals"
value="12"
color="blue"
/>
<StatCard
icon={Briefcase}
label="Open Positions"
value="5"
color="emerald"
/>
</div>
{/* Two Column Layout */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Pending Approvals */}
<div className="bg-white rounded-2xl shadow-lg shadow-gray-200/50 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">
Pending Approvals
</h2>
<div className="space-y-3">
{pendingApprovals.map(approval => (
<ApprovalCard key={approval.id} {...approval} />
))}
</div>
</div>
{/* Recent Activity */}
<div className="bg-white rounded-2xl shadow-lg shadow-gray-200/50 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">
Recent Activity
</h2>
<div className="space-y-4">
{activities.map(activity => (
<ActivityItem key={activity.id} {...activity} />
))}
</div>
</div>
</div>
{/* Department Distribution Chart */}
<div className="bg-white rounded-2xl shadow-lg shadow-gray-200/50 p-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4">
Employees by Department
</h2>
<div className="h-64">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={departmentData}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
paddingAngle={2}
dataKey="value"
>
{departmentData.map((entry, index) => (
<Cell key={index} fill={chartColors[index % 6]} />
))}
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
</div>
</div>
</div>
);
};Stat Card Component (Gradient Version)
Stats cards MUST use gradient backgrounds. This is what makes the app look polished.
// components/dashboard/StatCard.tsx
interface StatCardProps {
title: string;
value: string | number;
icon: LucideIcon;
variant: 'blue' | 'amber' | 'violet' | 'emerald';
trend?: string;
trendUp?: boolean;
}
const variants = {
blue: {
gradient: 'from-blue-500 to-blue-600',
muted: 'text-blue-100',
},
amber: {
gradient: 'from-amber-400 to-orange-500',
muted: 'text-amber-100',
},
violet: {
gradient: 'from-purple-500 to-pink-500',
muted: 'text-violet-100',
},
emerald: {
gradient: 'from-emerald-400 to-green-500',
muted: 'text-emerald-100',
},
};
const StatCard = ({ title, value, icon: Icon, variant, trend, trendUp }: StatCardProps) => {
const { gradient, muted } = variants[variant];
return (
<div className={`bg-gradient-to-br ${gradient} rounded-2xl p-6 text-white shadow-lg`}>
<div className="flex items-center justify-between">
<div>
<p className={`text-sm font-medium ${muted}`}>{title}</p>
<p className="text-4xl font-semibold mt-1 tracking-tight">{value}</p>
{trend && (
<p className={`text-sm mt-2 ${muted}`}>
{trendUp ? '↑' : '↓'} {trend} from last month
</p>
)}
</div>
<div className="w-12 h-12 bg-white/20 rounded-2xl flex items-center justify-center">
<Icon className="w-6 h-6" />
</div>
</div>
</div>
);
};
// Usage in Dashboard:
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<StatCard title="Total Employees" value={156} icon={Users} variant="blue" trend="5%" trendUp />
<StatCard title="On Leave Today" value={8} icon={Palmtree} variant="amber" />
<StatCard title="Pending Approvals" value={12} icon={Clock} variant="violet" />
<StatCard title="Open Positions" value={5} icon={Briefcase} variant="emerald" />
</div>Key Features:
- Gradient background (NOT white)
- White text on colored background
- Semi-transparent icon container (
bg-white/20) - Large, bold value with
tracking-tight - Muted color for secondary text (e.g.,
text-blue-100) - Rounded corners (
rounded-2xl) - Shadow (
shadow-lg)
Employee Components
Employee Card (Clean Design)
// components/employees/EmployeeCard.tsx
const EmployeeCard = ({ employee }: { employee: Employee }) => {
return (
<div className="bg-white rounded-2xl shadow-lg shadow-gray-200/50 p-5 hover:shadow-xl transition-shadow">
<div className="flex items-start gap-4">
<img
src={employee.avatarUrl}
alt={employee.firstName}
className="w-12 h-12 rounded-full"
/>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-gray-900">
{employee.firstName} {employee.lastName}
</h3>
<p className="text-sm text-gray-500">{employee.jobTitle}</p>
</div>
</div>
<div className="mt-4 space-y-2">
<div className="flex items-center gap-2 text-sm text-gray-600">
<Mail size={14} className="text-gray-400" />
<span className="truncate">{employee.email}</span>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<MapPin size={14} className="text-gray-400" />
<span>{employee.location}</span>
</div>
</div>
<div className="mt-4 flex items-center gap-2">
<StatusBadge status={employee.status} />
<span className="text-xs px-2.5 py-1 bg-gray-100 text-gray-600 rounded-full">
{employee.department}
</span>
</div>
</div>
);
};
// Status Badge
const StatusBadge = ({ status }: { status: string }) => {
const styles = {
active: 'bg-emerald-100 text-emerald-700',
on_leave: 'bg-amber-100 text-amber-700',
terminated: 'bg-red-100 text-red-700',
};
const labels = {
active: 'Active',
on_leave: 'On Leave',
terminated: 'Terminated',
};
return (
<span className={cn(
"text-xs font-medium px-2.5 py-1 rounded-full",
styles[status]
)}>
{labels[status]}
</span>
);
};Employee Table (Desktop)
// components/employees/EmployeeTable.tsx
const EmployeeTable = ({ employees }: { employees: Employee[] }) => {
return (
<div className="bg-white rounded-2xl shadow-lg shadow-gray-200/50 overflow-hidden">
<table className="w-full">
<thead className="bg-gray-50 shadow-sm shadow-gray-100/50">
<tr>
<th className="text-left px-6 py-4 text-xs font-medium text-gray-500 uppercase tracking-wider">
Employee
</th>
<th className="text-left px-6 py-4 text-xs font-medium text-gray-500 uppercase tracking-wider">
Department
</th>
<th className="text-left px-6 py-4 text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th className="text-left px-6 py-4 text-xs font-medium text-gray-500 uppercase tracking-wider">
Location
</th>
<th className="text-right px-6 py-4"></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{employees.map(employee => (
<tr key={employee.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4">
<div className="flex items-center gap-3">
<img
src={employee.avatarUrl}
alt=""
className="w-10 h-10 rounded-full"
/>
<div>
<p className="font-medium text-gray-900">
{employee.firstName} {employee.lastName}
</p>
<p className="text-sm text-gray-500">{employee.jobTitle}</p>
</div>
</div>
</td>
<td className="px-6 py-4 text-sm text-gray-600">
{employee.department}
</td>
<td className="px-6 py-4">
<StatusBadge status={employee.status} />
</td>
<td className="px-6 py-4 text-sm text-gray-600">
{employee.location}
</td>
<td className="px-6 py-4 text-right">
<button className="p-2 hover:bg-gray-100 rounded-lg">
<MoreHorizontal size={16} className="text-gray-400" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};AI Chat Components
AI Floating Button (Glowing)
The AI button is the most noticeable element. It needs gradient + glow + hover effects.
// components/ai/AIFloatingButton.tsx
const AIFloatingButton = () => {
const { mode, setMode } = useAI();
if (mode !== 'minimized') return null;
return (
<motion.button
onClick={() => setMode('panel')}
className="
fixed z-50
bottom-24 right-6
md:bottom-8 md:right-8
w-14 h-14
rounded-full
bg-gradient-to-br from-purple-500 via-purple-400 to-pink-500
text-white
shadow-xl shadow-purple-500/30
hover:shadow-2xl hover:shadow-purple-500/40
flex items-center justify-center
"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: "spring", stiffness: 400, damping: 15 }}
>
<Sparkles className="w-6 h-6" />
</motion.button>
);
};Key Properties:
- iOS Purple→Pink gradient:
from-purple-500 via-purple-400 to-pink-500 - Glowing shadow:
shadow-xl shadow-purple-500/30 - Stronger hover glow:
shadow-2xl shadow-purple-500/40 - Scale on hover:
whileHover={{ scale: 1.1 }} - Spring animation on mount
- Position above bottom nav on mobile:
bottom-24 right-6
AI Chat Panel
// components/ai/AIChatPanel.tsx
const AIChatPanel = () => {
const { mode, setMode, messages, sendMessage, isTyping } = useAI();
const isMobile = useMediaQuery('(max-width: 768px)');
if (mode === 'minimized') return null;
// Mobile: Bottom sheet
if (isMobile) {
return (
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
className="fixed inset-x-0 bottom-0 z-50 bg-white rounded-t-2xl shadow-2xl max-h-[80vh] flex flex-col"
>
<ChatPanelContent />
</motion.div>
);
}
// Desktop: Side panel
return (
<motion.div
initial={{ x: '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
className="fixed top-0 right-0 bottom-0 w-96 z-50 bg-white shadow-2xl shadow-gray-300/40 flex flex-col"
>
<ChatPanelContent />
</motion.div>
);
};
const ChatPanelContent = () => {
const { setMode, messages, sendMessage, isTyping } = useAI();
const [input, setInput] = useState('');
return (
<>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 shadow-sm shadow-gray-100/50">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center">
<Sparkles size={16} className="text-white" />
</div>
<span className="font-semibold text-gray-900">AI Assistant</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setMode('fullscreen')}
className="p-2 hover:bg-gray-100 rounded-lg"
>
<Maximize2 size={18} className="text-gray-500" />
</button>
<button
onClick={() => setMode('minimized')}
className="p-2 hover:bg-gray-100 rounded-lg"
>
<X size={18} className="text-gray-500" />
</button>
</div>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map(msg => (
<ChatMessage key={msg.id} {...msg} />
))}
{isTyping && <TypingIndicator />}
</div>
{/* Input */}
<div className="p-4 shadow-sm shadow-gray-100/50">
<div className="flex items-center gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask anything..."
className="flex-1 px-4 py-2.5 bg-gray-100 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/30"
onKeyDown={(e) => e.key === 'Enter' && sendMessage(input)}
/>
<button
onClick={() => sendMessage(input)}
className="p-2.5 bg-violet-600 hover:bg-violet-700 text-white rounded-2xl transition-colors"
>
<Send size={18} />
</button>
</div>
</div>
</>
);
};Typing Indicator
// components/ai/TypingIndicator.tsx
const TypingIndicator = () => {
return (
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center">
<Sparkles size={14} className="text-white" />
</div>
<div className="flex items-center gap-1 px-4 py-2.5 bg-gray-100 rounded-2xl">
{[0, 1, 2].map((i) => (
<motion.div
key={i}
className="w-2 h-2 bg-gray-400 rounded-full"
animate={{ y: [0, -4, 0] }}
transition={{ duration: 0.6, repeat: Infinity, delay: i * 0.15 }}
/>
))}
</div>
</div>
);
};Tailwind Configuration
// tailwind.config.js
module.exports = {
darkMode: 'class',
theme: {
extend: {
colors: {
sidebar: {
DEFAULT: '#312e81',
hover: '#3730a3',
active: '#4f46e5',
},
},
animation: {
'slide-in-right': 'slide-in-right 0.3s ease-out',
'slide-in-left': 'slide-in-left 0.3s ease-out',
'slide-up': 'slide-up 0.3s ease-out',
},
keyframes: {
'slide-in-right': {
'0%': { transform: 'translateX(100%)' },
'100%': { transform: 'translateX(0)' },
},
'slide-in-left': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
},
'slide-up': {
'0%': { transform: 'translateY(100%)' },
'100%': { transform: 'translateY(0)' },
},
},
},
},
plugins: [
function({ addUtilities }) {
addUtilities({
'.safe-area-pb': {
paddingBottom: 'env(safe-area-inset-bottom)',
},
});
},
],
};Mock Data
// lib/mock-data.ts
export const mockEmployees = [
{
id: '1',
firstName: 'Sarah',
lastName: 'Chen',
email: 'sarah.chen@company.com',
jobTitle: 'Engineering Manager',
department: 'Engineering',
status: 'active',
location: 'San Francisco, CA',
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah',
},
{
id: '2',
firstName: 'Marcus',
lastName: 'Johnson',
email: 'marcus.johnson@company.com',
jobTitle: 'Senior Developer',
department: 'Engineering',
status: 'active',
location: 'San Francisco, CA',
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Marcus',
},
{
id: '3',
firstName: 'Emily',
lastName: 'Rodriguez',
email: 'emily.rodriguez@company.com',
jobTitle: 'Senior Product Designer',
department: 'Design',
status: 'on_leave',
location: 'New York, NY',
avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Emily',
},
// ... more employees
];
export const mockStats = {
totalEmployees: 156,
onLeaveToday: 8,
pendingApprovals: 12,
openPositions: 5,
};
export const departmentData = [
{ name: 'Engineering', value: 45 },
{ name: 'Sales', value: 25 },
{ name: 'Product', value: 12 },
{ name: 'Marketing', value: 10 },
{ name: 'Design', value: 8 },
{ name: 'HR', value: 6 },
];
export const chartColors = [
'#7c3aed', '#3b82f6', '#10b981', '#f59e0b', '#ec4899', '#06b6d4'
];Build Phases
Phase 1: Layout Foundation
- AppShell with responsive sidebar
- Collapsible sidebar (desktop)
- Mobile header with hamburger
- Mobile drawer navigation
- Bottom navigation (mobile)
Phase 2: Core Pages
- Dashboard with stats grid
- Employees page with table/cards
- Time Off page with calendar
- Documents page
Phase 3: AI Integration
- AI Provider context
- Floating AI button
- Chat panel (side panel desktop, bottom sheet mobile)
- Message components
Phase 4: Polish
- Smooth animations
- Loading states
- Empty states
- Responsive refinements
Quality Checklist
Visual Design (Most Important!)
- Stats cards have GRADIENT backgrounds (not white)
- Content cards use
shadow-lg shadow-gray-200/50(no borders) - All cards use
rounded-2xl(16px radius) - Buttons are pill-shaped (
rounded-full) - AI button has violet gradient AND glow shadow
- Generous whitespace (
p-6in cards,gap-6between) - Looks like Apple.com, NOT a wireframe
Layout
- Sidebar visible on desktop (1024px+)
- Sidebar uses iOS Dark Mode (
neutral-900) - Sidebar primary button has gradient
- Bottom nav visible on mobile only
- Content area has white background
AI
- Floating button visible on all pages
- Button positioned above bottom nav on mobile
- Button has scale animation on hover
- Chat panel opens smoothly
Mobile
- Touch targets are 44px+ minimum
- Safe area padding for iOS
- No horizontal scroll issues