Skip to main content
The Auction Platform uses a comprehensive theming system built with CSS custom properties and React context. The system supports light and dark themes with seamless switching and persistence.

Theme Architecture

The theming system consists of three main layers:
  1. Design Tokens - Base values for spacing, typography, shadows, etc.
  2. Theme Variables - Color values that change per theme
  3. Theme Provider - React context for managing theme state

Theme Provider

The ThemeProvider component manages theme state and persists user preferences to localStorage.
import { createContext, useContext, useEffect, useState } from "react"

type Theme = "white" | "dark"
const STORAGE_KEY = "theme"

const ThemeContext = createContext<{ theme: Theme, setTheme: (t: Theme) => void } | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
    const [theme, setTheme] = useState<Theme>(() => {
        const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
        return stored ?? 'dark'
    });

    useEffect(() => {
        document.documentElement.setAttribute("data-theme", theme);
        localStorage.setItem(STORAGE_KEY, theme)
    }, [theme]);

    return <ThemeContext.Provider value={{ theme, setTheme }}>
        {children}
    </ThemeContext.Provider>
}

export function useTheme() {
    const ctx = useContext(ThemeContext);
    if (!ctx) throw new Error("useTheme must ne used inside ThemeProvider")
    return ctx
}

Key Features

  • Persistent Preferences - Theme choice saved to localStorage
  • Default Theme - Falls back to dark if no preference exists
  • Data Attribute - Sets data-theme on document root for CSS targeting

Using the useTheme Hook

Access and modify the current theme from any component:
import { useTheme } from '@app/providers/Theme';

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

    return (
        <div>
            <button onClick={() => setTheme("white")}>Light</button>
            <button onClick={() => setTheme("dark")}>Dark</button>
            <p>Current theme: {theme}</p>
        </div>
    );
}
The useTheme hook must be used inside a component wrapped by ThemeProvider.

Design Tokens

Base design tokens are defined in src/shared/styles/tokens.css and apply to all themes.

Spacing Scale

:root {
  --space-2: 2px;
  --space-4: 4px;
  --space-8: 8px;
  --space-12: 12px;
  --space-16: 16px;
  --space-20: 20px;
  --space-24: 24px;
  --space-32: 32px;
  --space-40: 40px;
  --space-48: 48px;
  --space-64: 64px;
  --space-80: 80px;
}

Typography

:root {
  --font-family: Figtree, Roboto, Noto Sans Hebrew, Noto Kufi Arabic, Noto Sans JP, sans-serif;
  --title-font-family: Poppins, Roboto, Noto Sans Hebrew, Noto Kufi Arabic, Noto Sans JP, sans-serif;

  /* Responsive font sizes */
  --font-size-text-xs: 0.6875rem;  /* 11px */
  --font-size-text-sm: 0.75rem;    /* 12px */
  --font-size-text-md: 0.875rem;   /* 14px */
  --font-size-text-base: 1rem;     /* 16px */

  /* Font weights */
  --font-weight-light: 200;
  --font-weight-normal: 400;
  --font-weight-medium: 600;
  --font-weight-bold: 700;
}

Shadows & Effects

:root {
  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 10px 24px rgba(0, 0, 0, 0.12);

  /* Transitions */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --duration-fast: 150ms;
  --duration-normal: 250ms;
  --duration-slow: 400ms;
}

Theme Colors

Theme-specific colors are defined in src/shared/styles/themes.css.

Light Theme (White)

:root[data-theme="white"] {
  /* Backgrounds */
  --color-bg: #f5f5f5;
  --color-surface: #d3e2de;
  --color-border: #e2e2e2;

  /* Typography */
  --color-text-primary: #172726;
  --color-text-secondary: #243f3d;
  --color-text-muted: #476664;

  /* Accent */
  --color-accent: #f95831;

  /* Status */
  --color-success: #3fa67a;
  --color-danger: #d64545;

  /* Highlight */
  --color-highlight-bg: rgba(0, 0, 0, 0.06);
}

Dark Theme

:root[data-theme="dark"] {
  /* Backgrounds */
  --color-bg: #1c1c1c;
  --color-surface: #1d1d1d;
  --color-border: #262626;

  /* Typography */
  --color-text-primary: #f2f2f2;
  --color-text-secondary: #bfbfbf;
  --color-text-muted: #8c8c8c;

  /* Primary */
  --color-primary: #ffffff;
  --color-primary-hover: #e6e6e6;

  /* Accent */
  --color-accent: #ffffff;

  /* Status */
  --color-success: #4cc38a;
  --color-danger: #ff6b6b;

  /* Highlight */
  --color-highlight-bg: rgba(255, 255, 255, 0.08);
}

Using Theme Variables

Reference theme variables in your CSS modules:
.card {
  background: var(--color-surface);
  color: var(--color-text-primary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-8);
  padding: var(--space-layout-md);
  box-shadow: var(--shadow-md);
}

.button {
  background: var(--color-accent);
  color: var(--color-text-white);
  padding: var(--space-element-lg) var(--space-layout-sm);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.button:hover {
  transform: translateY(-2px);
}

Responsive Design Tokens

Font sizes and layout spacing adjust automatically based on screen size:
--font-size-text-base: 1rem;    /* 16px */
--space-layout-lg: 32px;

Best Practices

Always use --color-* variables instead of hardcoded values:
/* Good */
color: var(--color-text-primary);

/* Avoid */
color: #172726;
Use spacing tokens for consistent layouts:
/* Good */
padding: var(--space-layout-md);
margin-top: var(--space-element-lg);

/* Avoid */
padding: 24px;
margin-top: 12px;
Always verify your components work correctly in both light and dark themes.

Complete Example

Here’s a complete example showing theme-aware component styling:
import { useTheme } from '@app/providers/Theme';
import styles from './Card.module.css';

export function Card({ title, children }) {
    const { theme, setTheme } = useTheme();

    return (
        <div className={styles.card}>
            <h2 className={styles.title}>{title}</h2>
            <div className={styles.content}>{children}</div>
            
            <button 
                className={styles.themeToggle}
                onClick={() => setTheme(theme === 'dark' ? 'white' : 'dark')}
            >
                Switch to {theme === 'dark' ? 'Light' : 'Dark'} Mode
            </button>
        </div>
    );
}
Card.module.css
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-8);
  padding: var(--space-layout-md);
  box-shadow: var(--shadow-md);
}

.title {
  color: var(--color-text-primary);
  font-family: var(--title-font-family);
  font-size: var(--font-size-text-base);
  margin-bottom: var(--space-element-lg);
}

.content {
  color: var(--color-text-secondary);
  line-height: var(--line-height-normal);
}

.themeToggle {
  background: var(--color-accent);
  color: var(--color-text-white);
  padding: var(--space-element-lg) var(--space-layout-sm);
  border: none;
  border-radius: var(--radius-4);
  cursor: pointer;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.themeToggle:hover {
  opacity: 0.9;
}
For more examples, see src/tests/TestComponent.tsx which demonstrates comprehensive theme usage.

Build docs developers (and LLMs) love