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:
Design Tokens - Base values for spacing, typography, shadows, etc.
Theme Variables - Color values that change per theme
Theme Provider - React context for managing theme state
Theme Provider
The ThemeProvider component manages theme state and persists user preferences to localStorage.
src/app/providers/Theme.tsx
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 : 2 px ;
--space-4 : 4 px ;
--space-8 : 8 px ;
--space-12 : 12 px ;
--space-16 : 16 px ;
--space-20 : 20 px ;
--space-24 : 24 px ;
--space-32 : 32 px ;
--space-40 : 40 px ;
--space-48 : 48 px ;
--space-64 : 64 px ;
--space-80 : 80 px ;
}
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.6875 rem ; /* 11px */
--font-size-text-sm : 0.75 rem ; /* 12px */
--font-size-text-md : 0.875 rem ; /* 14px */
--font-size-text-base : 1 rem ; /* 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 1 px 2 px rgba ( 0 , 0 , 0 , 0.05 );
--shadow-md : 0 4 px 8 px rgba ( 0 , 0 , 0 , 0.08 );
--shadow-lg : 0 10 px 24 px rgba ( 0 , 0 , 0 , 0.12 );
/* Transitions */
--ease-standard : cubic-bezier ( 0.4 , 0 , 0.2 , 1 );
--duration-fast : 150 ms ;
--duration-normal : 250 ms ;
--duration-slow : 400 ms ;
}
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 : 1 px 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 ( -2 px );
}
Responsive Design Tokens
Font sizes and layout spacing adjust automatically based on screen size:
Mobile (default)
Tablet (768px+)
Desktop (1024px+)
--font-size-text-base: 1rem; /* 16px */
--space-layout-lg: 32px;
Best Practices
Use semantic color variables
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 {
background : var ( --color-surface );
border : 1 px 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.