Kulcare Docs
General

Tailwind CSS v4 Setup & Workflow

Status: ✅ Up to date for Tailwind CSS v4.2.x

Table of Contents

  1. Overview
  2. Architecture
  3. Package Dependencies
  4. globals.css Structure
  5. Design Token Flow
  6. Dark Mode
  7. MFE Integration
  8. Component Usage
  9. Troubleshooting

Overview

The Kulcare platform uses Tailwind CSS v4 with a CSS-first configuration approach. All styling is centralized in the @kulcare/ui-kit package, which is shared across all micro-frontend apps (MFEs).

Key Technologies

  • Tailwind CSS v4.2.x: Utility-first CSS framework with CSS-first configuration
  • CSS Custom Properties: Runtime theme switching with HSL color format
  • @tailwindcss/vite: Official Vite plugin for Tailwind v4
  • @theme inline: CSS-first theme configuration in Tailwind v4
  • tw-animate-css: Animation library (shadcn v4 compatible)

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        FRONTEND MONOREPO                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────┐                                               │
│  │   ui-kit    │  ← Theme provider (globals.css)               │
│  │  (shared)   │    • Design tokens (:root, .dark)             │
│  │             │    • @theme inline (Tailwind mappings)         │
│  └──────┬──────┘    • Components (button, card, dialog, etc.)  │
│         │                                                         │
│         │ @import                                                │
│         ▼                                                         │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    MFEs (Micro-Frontends)                  │  │
│  ├──────────┬──────────┬──────────┬──────────┬─────────────┤  │
│  │  shell   │   cms    │  growth  │  reports │   connect   │  │
│  │ :3000    │  :3001   │  :3003   │  :3004   │   :3005     │  │
│  └──────────┴──────────┴──────────┴──────────┴─────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Key Principles

  1. Single Source of Truth: One globals.css in ui-kit = consistent theme across all apps
  2. No CSS Duplication: Each app imports, Vite deduplicates during build
  3. Independent Builds: Each MFE can build independently or as part of shell
  4. CSS-First Config: Theme configuration uses @theme inline in CSS, not tailwind.config.js

Package Dependencies

ui-kit/package.json

{
  "devDependencies": {
    "@tailwindcss/vite": "^4.0.0",   // Vite plugin for Tailwind v4
    "tailwindcss": "4.2.2",            // Tailwind CSS v4
    "tw-animate-css": "^1.0.0"        // Animation library (shadcn v4 compatible)
  }
}

MFE package.json (e.g., shell, cms, growth, reports, connect)

{
  "devDependencies": {
    "@tailwindcss/vite": "^4.2.2",     // Vite plugin
    "tailwindcss": "^4.2.2"            // Tailwind CSS v4
  }
}

Removed Dependencies (No Longer Needed in v4)

"autoprefixer": "^10.4.x"  // ❌ Removed - handled by @tailwindcss/vite
"postcss": "^8.4.x"         // ❌ Removed - not used in v4
"tailwindcss": "^3.4.x"     // ❌ Removed - upgraded to v4

Vite Configuration

All MFEs use the same pattern

// apps/shell/vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),  // ← Tailwind v4 plugin
  ],
})

Why This Works

  • @tailwindcss/vite replaces the old PostCSS pipeline
  • It processes CSS at build time, providing 3-5x faster builds
  • No postcss.config.js or tailwind.config.js needed

globals.css Structure

/* ═══════════════════════════════════════════════════════════════
   1. FONTS & EXTERNAL IMPORTS
═══════════════════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Mulish...');
@import "react-day-picker/style.css";

/* ═══════════════════════════════════════════════════════════════
   2. TAILWIND v4 CORE
═══════════════════════════════════════════════════════════════ */
@import "tailwindcss";      // Tailwind's base styles
@import "tw-animate-css";  // Animation utilities

/* ═══════════════════════════════════════════════════════════════
   3. DESIGN TOKENS (:root - Light Mode)
═══════════════════════════════════════════════════════════════ */
:root {
  /* Core colors - Legacy app design system */
  --background: hsl(0 0% 96%);
  --foreground: hsl(228 14% 24%);
  
  /* Component colors */
  --card: hsl(0 0% 100%);
  --card-foreground: hsl(228 14% 24%);
  --primary: hsl(211 100% 52%);
  --primary-foreground: hsl(0 0% 100%);
  --secondary: hsl(0 0% 95%);
  --secondary-foreground: hsl(228 14% 24%);
  --muted: hsl(0 0% 95%);
  --muted-foreground: hsl(0 0% 44%);
  --accent: hsl(210 18% 92%);
  --accent-foreground: hsl(228 14% 24%);
  --destructive: hsl(0 84% 60%);
  --destructive-foreground: hsl(0 0% 100%);
  
  /* Utility colors */
  --border: hsl(210 18% 92%);
  --input: hsl(0 0% 85%);
  --ring: hsl(211 100% 52%);
  
  /* Radius - Legacy 3px base */
  --radius: 0.1875rem;
  
  /* Semantic - Legacy status colors */
  --success: hsl(142 71% 45%);      /* Green - universally understood as success */
  --warning: hsl(38 92% 50%);       /* Amber-orange - clear caution, high contrast */
  --info: hsl(199 89% 48%);         /* Sky blue - informational */
  
  /* Fonts */
  --font-sans: 'Mulish', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  
  /* Shadows - Light mode (subtle black shadows) */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.10);
  --shadow-lg: 0 7px 14px rgba(0, 0, 0, 0.12), 0 3px 6px rgba(0, 0, 0, 0.06);
  --shadow-xl: 0 5px 25px rgba(0, 0, 0, 0.15);
  
  /* Sidebar - Light mode (adaptive) */
  --sidebar: hsl(220 10% 94%);
  --sidebar-foreground: hsl(228 14% 24%);
  --sidebar-primary: hsl(211 100% 52%);
  --sidebar-primary-foreground: hsl(0 0% 100%);
  --sidebar-accent: hsl(211 100% 52% / 0.1);
  --sidebar-accent-foreground: hsl(211 100% 52%);
  --sidebar-border: hsl(210 18% 92%);
  --sidebar-ring: hsl(211 100% 52%);

  /* Sidebar Dimensions */
  --sidebar-width: 16rem;
  --sidebar-width-icon: 3rem;
  --sidebar-width-mobile: 18rem;

  /* Accent Palette (raw HSL channels for hsl() wrapping in @theme) */
  /* Hues spaced ≥15° apart from each other and from primary (211°) */
  --accent-indigo: 243 75% 59%;
  --accent-indigo-subtle: 243 75% 96%;
  --accent-purple: 262 83% 58%;
  --accent-purple-subtle: 262 83% 96%;
  --accent-pink: 336 80% 58%;
  --accent-pink-subtle: 336 80% 96%;
  --accent-cyan: 188 95% 42%;
  --accent-cyan-subtle: 188 95% 94%;
  --accent-teal: 172 66% 40%;
  --accent-teal-subtle: 172 66% 94%;
  --accent-orange: 24 94% 50%;
  --accent-orange-subtle: 24 94% 94%;

  /* Gray Scale (raw HSL channels) */
  --gray-50: 210 20% 98%;
  --gray-100: 210 20% 96%;
  --gray-200: 214 20% 91%;
  --gray-300: 213 20% 86%;
  --gray-400: 215 16% 77%;
  --gray-500: 215 16% 47%;
  --gray-600: 215 19% 35%;
  --gray-700: 215 25% 27%;
  --gray-800: 217 33% 17%;
  --gray-900: 222 47% 11%;
  --gray-foreground: 0 0% 98%;
}

/* ═══════════════════════════════════════════════════════════════
   4. DESIGN TOKENS (.dark - Dark Mode)
═══════════════════════════════════════════════════════════════ */
.dark {
  --background: hsl(220 10% 10%);
  --foreground: hsl(0 0% 98%);
  --primary: hsl(211 100% 52%);
  --primary-foreground: hsl(0 0% 100%);
  --secondary: hsl(220 10% 15%);
  --secondary-foreground: hsl(0 0% 98%);
  --muted: hsl(220 10% 15%);
  --muted-foreground: hsl(0 0% 65%);
  --accent: hsl(220 10% 20%);
  --accent-foreground: hsl(0 0% 98%);
  --destructive: hsl(0 84% 55%);
  --destructive-foreground: hsl(0 0% 100%);
  --border: hsl(220 10% 20%);
  --input: hsl(220 10% 18%);
  
  /* Shadows - Dark mode (higher opacity for visibility) */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.15);
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.20);
  --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.25);
  --shadow-lg: 0 7px 14px rgba(0, 0, 0, 0.30), 0 3px 6px rgba(0, 0, 0, 0.15);
  --shadow-xl: 0 5px 25px rgba(0, 0, 0, 0.15);
  
  /* Sidebar - Dark mode (Ant Design dark sider #001529) */
  --sidebar: hsl(220 27% 8%);
  --sidebar-foreground: hsl(0 0% 65%);
  --sidebar-primary: hsl(211 100% 52%);
  --sidebar-primary-foreground: hsl(0 0% 100%);
  --sidebar-accent: hsl(220 13% 14%);
  --sidebar-accent-foreground: hsl(211 100% 52%);
  --sidebar-border: hsl(220 27% 12%);
  --sidebar-ring: hsl(211 100% 52%);

  /* Semantic Status (dark) - desaturated + darkened for dark bg contrast */
  --success: hsl(142 60% 38%);
  --warning: hsl(38 88% 45%);
  --info: hsl(199 89% 48%);

  /* Accent Palette — Dark Overrides */
  /* Lighter hues for legibility on dark surfaces; subtle variants use 14-16% lightness */
  --accent-indigo: 243 70% 68%;
  --accent-indigo-subtle: 243 70% 16%;
  --accent-purple: 262 75% 65%;
  --accent-purple-subtle: 262 75% 16%;
  --accent-pink: 336 72% 62%;
  --accent-pink-subtle: 336 72% 16%;
  --accent-cyan: 188 88% 50%;
  --accent-cyan-subtle: 188 88% 14%;
  --accent-teal: 172 60% 45%;
  --accent-teal-subtle: 172 60% 14%;
  --accent-orange: 24 90% 54%;
  --accent-orange-subtle: 24 90% 16%;

  /* Gray Scale — Dark Overrides */
  --gray-50: 220 10% 10%;
  --gray-100: 220 10% 15%;
  --gray-200: 220 10% 20%;
  --gray-300: 220 10% 25%;
  --gray-400: 220 10% 35%;
  --gray-500: 220 10% 50%;
  --gray-600: 220 10% 65%;
  --gray-700: 220 10% 75%;
  --gray-800: 220 10% 85%;
  --gray-900: 220 10% 92%;
  --gray-foreground: 0 0% 98%;
}

/* ═══════════════════════════════════════════════════════════════
   5. TAILWIND MAPPING (@theme inline)
═══════════════════════════════════════════════════════════════ */
@theme inline {
  /* Core Colors */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);

  /* Semantic Status */
  --color-success: var(--success);
  --color-warning: var(--warning);
  --color-info: var(--info);

  /* Sidebar */
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);

  /* Sidebar Dimensions */
  --sidebar-width: 16rem;
  --sidebar-width-icon: 3rem;
  --sidebar-width-mobile: 18rem;

  /* Radius Scale */
  --radius: var(--radius);
  --radius-sm: calc(var(--radius) * 1.067);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) * 3.2);
  --radius-xl: 0.5rem;

  /* Fonts */
  --font-sans: var(--font-sans);
  --font-mono: var(--font-mono);

  /* Shadows */
  --shadow-sm: var(--shadow-sm);
  --shadow: var(--shadow);
  --shadow-md: var(--shadow-md);
  --shadow-lg: var(--shadow-lg);
  --shadow-xl: 0 5px 25px rgba(0, 0, 0, 0.15);

  /* Chart Colors (recharts) */
  --color-chart-1: var(--primary);
  --color-chart-2: var(--secondary);
  --color-chart-3: var(--accent);
  --color-chart-4: var(--muted);
  --color-chart-5: var(--destructive);

  /* Typography Scale */
  --text-xs: 0.75rem;
  --text-xs--line-height: 1.5;
  --text-sm: 0.75rem;
  --text-sm--line-height: 1.5;
  --text-base: 0.875rem;
  --text-base--line-height: 1.5;
  --text-md: 1rem;
  --text-md--line-height: 1.5;
  --text-lg: 1.125rem;
  --text-lg--line-height: 1.4;
  --text-xl: 1.25rem;
  --text-xl--line-height: 1.35;
  --text-2xl: 1.375rem;
  --text-2xl--line-height: 1.35;
  --text-3xl: 1.5rem;
  --text-3xl--line-height: 1.3;

  /* Leading Tokens */
  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.75;

  /* Accent Palette */
  --color-accentIndigo: hsl(var(--accent-indigo));
  --color-accentIndigo-subtle: hsl(var(--accent-indigo-subtle));
  --color-accent-purple: hsl(var(--accent-purple));
  --color-accent-purple-subtle: hsl(var(--accent-purple-subtle));
  --color-accentPink: hsl(var(--accent-pink));
  --color-accentPink-subtle: hsl(var(--accent-pink-subtle));
  --color-accentCyan: hsl(var(--accent-cyan));
  --color-accentCyan-subtle: hsl(var(--accent-cyan-subtle));
  --color-accentTeal: hsl(var(--accent-teal));
  --color-accentTeal-subtle: hsl(var(--accent-teal-subtle));
  --color-accentOrange: hsl(var(--accent-orange));
  --color-accentOrange-subtle: hsl(var(--accent-orange-subtle));

  /* Gray Scale */
  --color-gray-50: hsl(var(--gray-50));
  --color-gray-100: hsl(var(--gray-100));
  --color-gray-200: hsl(var(--gray-200));
  --color-gray-300: hsl(var(--gray-300));
  --color-gray-400: hsl(var(--gray-400));
  --color-gray-500: hsl(var(--gray-500));
  --color-gray-600: hsl(var(--gray-600));
  --color-gray-700: hsl(var(--gray-700));
  --color-gray-800: hsl(var(--gray-800));
  --color-gray-900: hsl(var(--gray-900));

  /* Kulcare Brand Colors */
  --color-kulcare-primary: #078DFF;
  --color-kulcare-primary-light: #3DA4FF;
  --color-kulcare-primary-dark: #0570CC;
  --color-kulcare-secondary: #722ed1;

  /* App Identity Colors (do not theme-switch) */
  --color-cms: #078DFF;
  --color-cms-light: #e6f4ff;
  --color-cms-dark: #0570CC;
  --color-cms-accent: #3DA4FF;
  --color-growth: #52c41a;
  --color-growth-light: #f6ffed;
  --color-growth-dark: #389e0d;
  --color-growth-accent: #73d13d;
  --color-doctor: #722ed1;
  --color-doctor-light: #f9f0ff;
  --color-doctor-dark: #531dab;
  --color-doctor-accent: #9254de;
  --color-connect: #1890ff;
  --color-connect-light: #e6f7ff;
  --color-connect-dark: #096dd9;
  --color-connect-accent: #40a9ff;
  --color-network: #fa8c16;
  --color-network-light: #fff7e6;
  --color-network-dark: #d46b08;
  --color-network-accent: #ffa940;
  --color-hospital: #eb2f96;
  --color-hospital-light: #fff0f6;
  --color-hospital-dark: #c41d7f;
  --color-hospital-accent: #f759ab;
  --color-reports: #13c2c2;
  --color-reports-light: #e6fffb;
  --color-reports-dark: #08979c;
  --color-reports-accent: #36cfc9;
}

/* ═══════════════════════════════════════════════════════════════
   6. BASE STYLES
═══════════════════════════════════════════════════════════════ */
/* Override Tailwind v4's default border-color (currentColor) */
* {
  border-color: var(--border);
}

html {
  font-family: var(--font-sans);
}

body {
  background: var(--background);
  color: var(--foreground);
}

/* ═══════════════════════════════════════════════════════════════
   7. CUSTOM UTILITIES (@utility)
═══════════════════════════════════════════════════════════════ */
@utility scrollbar-hide {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

File Location

packages/ui-kit/src/styles/globals.css

Design Token Flow

┌─────────────────────────────────────────────────────────────────┐
│                        DESIGN TOKEN FLOW                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. CSS Variables (globals.css)                                 │
│     :root { --primary: hsl(211 100% 52%); }                    │
│     .dark  { --primary: hsl(211 90% 56%); }                    │
│                         │                                       │
│                         ▼                                       │
│  2. @theme inline mapping                                       │
│     @theme inline {                                             │
│       --color-primary: var(--primary);  // Maps to bg-primary   │
│     }                                                           │
│                         │                                       │
│                         ▼                                       │
│  3. Tailwind Utilities (in components)                          │
│     <div className="bg-primary text-primary-foreground">        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Token Naming Convention

CSS VariableTailwind UtilityUsage
--backgroundbg-background, text-backgroundPage background
--foregroundtext-foregroundPrimary text
--primarybg-primary, text-primaryBrand color
--primary-foregroundtext-primary-foregroundText on primary
--secondarybg-secondarySecondary elements
--mutedbg-muted, text-muted-foregroundSubtle backgrounds
--destructivebg-destructiveError/danger states
--borderborder, border-borderBorders
--radiusrounded, rounded-sm, etc.Border radius
--sidebarbg-sidebar, text-sidebar-foregroundSidebar (adaptive)
--sidebar-primarybg-sidebar-primary, text-sidebar-primary-foregroundSidebar active item

Dark Mode

How Dark Mode Works

  1. User toggles dark mode → Adds/removes .dark class on <html> element

  2. CSS variable cascade:

    /* Without .dark class - uses :root values */
    --primary: hsl(211 100% 52%);  // Light blue
    
    /* With .dark class - .dark values override */
    .dark {
      --primary: hsl(211 90% 56%);  // Brighter blue
    }
  3. @theme inline propagates to Tailwind:

    .dark { --primary: hsl(211 90% 56%); }
    @theme inline { --color-primary: var(--primary); }
    /* → bg-primary now uses the dark mode value automatically */

Implementation

// ThemeProvider.tsx
import { useTheme } from '@/hooks/use-theme'

function App() {
  const { theme, setTheme } = useTheme()
  
  return (
    <div className={theme}>
      {/* App content */}
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  )
}

MFE Integration

How Each MFE Gets Styles

Each MFE imports the shared globals.css:

/* apps/shell/src/index.css */
@import "@kulcare/ui-kit/src/styles/globals.css";

/* Animation for line charts */
@keyframes draw {
  to {
    stroke-dashoffset: 0;
  }
}

.animate-draw {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: draw 2s ease-out forwards;
}
/* apps/cms/src/index.css */
@import "@kulcare/ui-kit/src/styles/globals.css";

/* CMS-specific video session styles... */

MFE Package Configuration

AppPortTailwind VersionStatus
shell3000v4.2.2✅ Production
cms3001v4.2.2✅ Production
growth3003v4.2.2✅ Production
reports3004v4.2.2✅ Production
connect3005v4.2.2✅ Production
ui-kit-v4.2.2✅ Theme provider

Component Usage

Using Theme Tokens in Components

// ✅ Good - Uses theme tokens
<div className="bg-primary text-primary-foreground">
  <button className="bg-destructive hover:bg-destructive/90">
    Delete
  </button>
</div>

// ❌ Bad - Hardcoded colors
<div className="bg-blue-500 text-white">
  <button className="bg-red-600 hover:bg-red-700">
    Delete
  </button>
</div>

Using shadcn Components

import { Button } from "@kulcare/ui-kit"
import { Card, CardHeader, CardContent } from "@kulcare/ui-kit"

function MyPage() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Patient Details</CardTitle>
      </CardHeader>
      <CardContent>
        <Button variant="default">Save</Button>
        <Button variant="destructive">Delete</Button>
        <Button variant="outline">Cancel</Button>
      </CardContent>
    </Card>
  )
}

Button Variants

VariantUsage
defaultPrimary actions - bg-primary text-primary-foreground
destructiveDanger actions - bg-destructive text-destructive-foreground
outlineSecondary actions - border border-input bg-background
secondaryLess prominent - bg-secondary text-secondary-foreground
ghostSubtle actions - hover:bg-accent hover:text-accent-foreground
linkText links - text-primary underline-offset-4 hover:underline

Troubleshooting

LSP Shows "Tailwind-specific syntax is disabled"

This is an IDE warning only, not a build error. The LSP doesn't understand Tailwind v4's @theme inline syntax.

Solution: Install the Tailwind CSS IntelliSense extension for VS Code, or ignore the warning - builds work correctly.

Styles Not Applying in MFE

  1. Verify the MFE imports globals.css:

    /* apps/*/src/index.css */
    @import "@kulcare/ui-kit/src/styles/globals.css";
  2. Verify the MFE has @tailwindcss/vite in its vite.config.ts:

    import tailwindcss from '@tailwindcss/vite'
    plugins: [react(), tailwindcss()]

Dark Mode Not Working

  1. Verify .dark class is being applied to <html> or <body>
  2. Check that CSS variables are defined in both :root and .dark
  3. Verify @theme inline references use var() not direct values

Build Errors After Upgrading

  1. Clear the build cache:

    pnpm clean
    pnpm install
    pnpm build
  2. Check for mixed Tailwind versions:

    pnpm list tailwindcss

Migration Notes

Upgrading from v3 to v4

See Migration Plan for detailed instructions.

Key Changes from v3

v3 (Old)v4 (New)
tailwind.config.js@theme inline in CSS
@tailwind base/components/utilities@import "tailwindcss"
tailwindcss-animatetw-animate-css
PostCSS plugin@tailwindcss/vite
hsl(var(--x))var(--x) directly
shadow-xsshadow-sm (shadow-xs removed in v4)

External References

On this page