Bluewoo HRMS
Micro-Step Build PlanBuilding BlocksUX/UI Design

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

ElementDesign PrincipleImplementation
BackgroundApple-style grouped#F2F2F7 (gray-100) main, white cards
SidebarTheme-awareWhite (light) / #1C1C1E (dark mode)
Stats CardsColorful gradientsBlue, amber→orange, violet→purple, emerald→green
Content CardsMinimal with depthWhite + soft shadows + dark mode variant
ButtonsPrimary blue#008CF0, pill-shaped with scale effects
AI ButtonViolet→FuchsiaViolet→purple→fuchsia gradient with glow
TypographySemantic colorscard-foreground, muted-foreground
TransitionsSpring-basedFramer 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

TechnologyVersionPurpose
React19.xUI library with TypeScript
Tailwind CSS3.4Utility-first styling
shadcn/uiLatestAccessible components
React Routerv6Navigation
Framer MotionLatestAnimations
Lucide ReactLatestIcons
RechartsLatestCharts
date-fnsLatestDates

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)

ElementCorrect Implementation
Stats cardsbg-gradient-to-br from-blue-500 to-blue-600 text-white rounded-2xl shadow-lg p-6
Content cardsbg-white rounded-2xl shadow-lg shadow-gray-200/50 p-6 (NO borders!)
Buttonsbg-blue-500 text-white rounded-full px-6 py-2.5
Shadowsshadow-lg or shadow-xl with subtle gray tint
Cornersrounded-2xl (16px) for cards
Spacingp-6 inside cards, gap-6 between cards

DO NOT (Common Mistakes)

MistakeWhy It's Wrong
Plain white stats cardsLooks like a wireframe
border border-gray-200Use shadows, not borders
rounded-lg (8px)Too small, use rounded-2xl
Gray everywhereUse 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.tsx

Layout 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>
  );
};
// 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-6 in cards, gap-6 between)
  • 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