link,[object Object]
Skip to content

Theming System

Overview

AcqMarketplace implements a comprehensive theming system based on CSS custom properties (variables) with support for dark and light modes. The system uses Tailwind CSS semantic tokens and shadcn/ui components for consistent design.

Theme Architecture

CSS Variables Foundation

File: src/index.css:7-122

The theming system is built on CSS custom properties defined at the root level:

css
:root {
  /* Core semantic colors */
  --background: 210 20% 98%;     /* #F5F7FA */
  --foreground: 210 6% 21%;      /* #333D4C */
  --primary: 0 65% 58%;          /* Brand Red #DB5151 */
  --secondary: 210 17% 94%;      /* #EEF1F6 */
  
  /* System colors */
  --destructive: 0 84% 60%;      /* #F03D3D */
  --success: 150 64% 45%;        /* #33B36B */
  --warning: 24 100% 59%;        /* #FC9231 */
  --info: 186 35% 36%;           /* #3D7A81 */
  
  /* UI elements */
  --border: 220 20% 86%;
  --input: 220 20% 91%;
  --card: 0 0% 100%;
  --popover: 0 0% 100%;
  --muted: 210 17% 94%;
  --accent: 210 17% 94%;
}

.dark {
  /* Dark mode overrides */
  --background: 222 20% 9%;      /* #111827 */
  --foreground: 210 4% 98%;
  --primary: 0 65% 65%;          /* Adjusted for dark */
  /* ... other dark mode values */
}

HSL Color Format

All colors use HSL (Hue, Saturation, Lightness) format without the hsl() wrapper:

  • Format: hue saturation% lightness%
  • Usage: hsl(var(--primary))
  • Benefits: Easy manipulation and consistent alpha channel support

Theme Provider Implementation

ThemeProvider Component

File: src/components/ThemeProvider.tsx

typescript
type Theme = \"dark\" | \"light\" | \"system\"

interface ThemeProviderProps {
  children: React.ReactNode
  defaultTheme?: Theme
  storageKey?: string
}

export function ThemeProvider({
  children,
  defaultTheme = \"system\",
  storageKey = \"vite-ui-theme\",
}: ThemeProviderProps) {
  const [theme, setTheme] = useState<Theme>(
    () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
  )

  useEffect(() => {
    const root = window.document.documentElement
    root.classList.remove(\"light\", \"dark\")

    if (theme === \"system\") {
      const systemTheme = window.matchMedia(\"(prefers-color-scheme: dark)\")
        .matches ? \"dark\" : \"light\"
      root.classList.add(systemTheme)
      return
    }

    root.classList.add(theme)
  }, [theme])

  const value = {
    theme,
    setTheme: (theme: Theme) => {
      localStorage.setItem(storageKey, theme)
      setTheme(theme)
    },
  }

  return (
    <ThemeProviderContext.Provider value={value}>
      {children}
    </ThemeProviderContext.Provider>
  )
}

Using the Theme

typescript
import { useTheme } from "@/components/ThemeProvider"

function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <Button
      variant="outline"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  )
}

Tailwind CSS Integration

Tailwind Configuration

File: tailwind.config.ts:26-87

typescript
theme: {
  extend: {
    colors: {
      // Semantic colors from CSS variables
      border: \"hsl(var(--border))\",
      input: \"hsl(var(--input))\",
      ring: \"hsl(var(--ring))\",
      background: \"hsl(var(--background))\",
      foreground: \"hsl(var(--foreground))\",
      primary: {
        DEFAULT: \"hsl(var(--primary))\",
        foreground: \"hsl(var(--primary-foreground))\",
      },
      secondary: {
        DEFAULT: \"hsl(var(--secondary))\",
        foreground: \"hsl(var(--secondary-foreground))\",
      },
      // ... other semantic colors
    },
  },
}

Usage in Components

typescript
// Using semantic tokens
<div className="bg-background text-foreground">
  <button className="bg-primary text-primary-foreground hover:bg-primary/90">
    Primary Action
  </button>
  <div className="border border-border rounded-lg p-4">
    <p className="text-muted-foreground">Secondary content</p>
  </div>
</div>

Brand Color Palette

Primary Brand Colors

css
/* Brand Red - Primary Action Color */
--primary: 0 65% 58%;           /* #DB5151 */

/* Brand Gray Scale */
--gray-50: #F5F7FA;
--gray-100: #EEF1F6;
--gray-200: #E0E5EB;
--gray-300: #CAD0D9;
--gray-400: #9CA3AF;
--gray-500: #6C727F;
--gray-600: #4E5562;
--gray-700: #333D4C;
--gray-800: #1D2735;
--gray-900: #111827;
--gray-950: #030712;

System Status Colors

css
/* Success */
--success: 150 64% 45%;         /* #33B36B */

/* Warning */
--warning: 24 100% 59%;         /* #FC9231 */

/* Error/Destructive */
--destructive: 0 84% 60%;       /* #F03D3D */

/* Info */
--info: 186 35% 36%;           /* #3D7A81 */

Component Theming

shadcn/ui Component Styling

Components automatically inherit theme colors through CSS variables:

typescript
// Button component variations
<Button variant="default">Primary Button</Button>      // Uses --primary
<Button variant="secondary">Secondary Button</Button>   // Uses --secondary
<Button variant="destructive">Delete Button</Button>    // Uses --destructive
<Button variant="outline">Outline Button</Button>       // Uses --border
<Button variant="ghost">Ghost Button</Button>           // Transparent

Custom Component Theming

typescript
function ThemedCard({ children, variant = "default" }: {
  children: React.ReactNode;
  variant?: "default" | "accent" | "muted";
}) {
  const variantStyles = {
    default: "bg-card text-card-foreground border-border",
    accent: "bg-accent text-accent-foreground border-accent",
    muted: "bg-muted text-muted-foreground border-muted",
  };

  return (
    <div className={cn(
      "rounded-lg border p-6 shadow-sm",
      variantStyles[variant]
    )}>
      {children}
    </div>
  );
}

Dark Mode Implementation

Automatic System Preference

typescript
// Detects system preference
const prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;

// Listen for system changes
useEffect(() => {
  const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\");
  
  const handleChange = (e: MediaQueryListEvent) => {
    if (theme === \"system\") {
      document.documentElement.classList.toggle(\"dark\", e.matches);
    }
  };
  
  mediaQuery.addEventListener(\"change\", handleChange);
  return () => mediaQuery.removeEventListener(\"change\", handleChange);
}, [theme]);

Dark Mode Specific Styles

css
/* Dark mode specific utilities */
.dark .glass {
  @apply bg-black/5;
}

.dark .glass::before {
  background: linear-gradient(225deg, rgba(255,255,255,0.1), rgba(255,255,255,0.02));
}

.dark .diagonal-bg::before {
  opacity: 0.1;
}

Special Effects and Utilities

Glass Morphism Effect

File: src/index.css:178-201

css
.glass {
  @apply bg-white/5 backdrop-blur-lg dark:bg-black/5;
  border: 1px solid transparent;
  background-clip: padding-box;
  position: relative;
}

.glass::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(225deg, rgba(255,255,255,0.2), rgba(255,255,255,0.05));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

Gradient Utilities

css
.text-gradient {
  @apply bg-clip-text text-transparent bg-gradient-to-r from-primary to-[hsl(var(--success))];
}

.button-gradient {
  @apply bg-gradient-to-r from-primary to-[hsl(var(--success))] hover:opacity-90 transition-opacity rounded-full;
}

Animation and Transitions

css
.card-hover {
  @apply transition-all duration-300 hover:shadow-medium hover:scale-105 hover:border-accent;
}

.glass-hover {
  @apply transition-all duration-300 hover:bg-white/10 dark:hover:bg-black/10;
}

Typography System

Font Configuration

File: tailwind.config.ts:22-25

typescript
fontFamily: {
  'onest': ['Onest', 'sans-serif'],
  'sans': ['Onest', 'system-ui', 'sans-serif'],
}

Typography Utilities

css
body {
  @apply bg-background text-foreground font-sans antialiased;
  font-feature-settings: \"rlig\" 1, \"calt\" 1;
}

Shadow System

Custom Shadow Variables

File: src/index.css:62-66 and src/index.css:117-120

css
:root {
  --shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-medium: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  --shadow-large: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}

.dark {
  --shadow-small: 0 1px 2px 0 rgb(0 0 0 / 0.3);
  --shadow-medium: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4);
  --shadow-large: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.4);
}

Shadow Utilities

css
.shadow-small { box-shadow: var(--shadow-small); }
.shadow-medium { box-shadow: var(--shadow-medium); }
.shadow-large { box-shadow: var(--shadow-large); }

Theme Customization

Adding New Color Tokens

  1. Define CSS Variables:
css
:root {
  --brand-blue: 210 100% 50%;
  --brand-blue-foreground: 0 0% 100%;
}

.dark {
  --brand-blue: 210 100% 60%;
  --brand-blue-foreground: 210 100% 10%;
}
  1. Add to Tailwind Config:
typescript
colors: {
  'brand-blue': {
    DEFAULT: \"hsl(var(--brand-blue))\",
    foreground: \"hsl(var(--brand-blue-foreground))\",
  },
}
  1. Use in Components:
typescript
<Button className="bg-brand-blue text-brand-blue-foreground">
  Custom Button
</Button>

Creating Theme Variants

typescript
// Create a theme variant hook
function useThemeVariant() {
  const { theme } = useTheme();
  const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
  
  useEffect(() => {
    if (theme === 'system') {
      const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches 
        ? 'dark' 
        : 'light';
      setResolvedTheme(systemTheme);
    } else {
      setResolvedTheme(theme as 'light' | 'dark');
    }
  }, [theme]);
  
  return resolvedTheme;
}

Best Practices

1. Always Use Semantic Tokens

typescript
// ✅ Good - Uses semantic tokens
<div className="bg-background text-foreground border border-border">

// ❌ Bad - Uses direct colors
<div className="bg-white text-black border border-gray-200">

2. Provide Dark Mode Alternatives

typescript
// ✅ Good - Automatic theme support
<div className="bg-card text-card-foreground">

// ✅ Good - Explicit dark mode handling
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">

3. Test Both Themes

typescript
// Component should work in both themes
function ThemedComponent() {
  return (
    <div className="bg-background text-foreground">
      <div className="bg-card border border-border rounded-lg p-4">
        <p className="text-muted-foreground">This works in both themes</p>
      </div>
    </div>
  );
}

4. Use Alpha Channels Consistently

typescript
// ✅ Good - Uses alpha with semantic tokens
<div className="bg-primary/10 border border-primary/20">

// ✅ Good - Explicit alpha values
<div className="bg-black/5 dark:bg-white/5">

Related Documentation: