-
Notifications
You must be signed in to change notification settings - Fork 54
Description
Implement a comprehensive dark mode theme toggle to provide users with a comfortable viewing experience in low-light conditions. The feature will include a theme switcher in the navbar, persist user preferences in localStorage, respect system settings, and ensure all UI components are properly styled for both light and dark themes.
Current State
The application currently supports only a light theme with hardcoded colors throughout 30+ CSS files. There is no theme management system, and users cannot switch between light and dark modes. This affects:
- All page components (Home, Games, Activities, Favorites)
- 13 game components
- 9 activity components
- Card components and common UI elements
- Layout and navigation components
Proposed Solution
Implement a theme system using React Context API combined with CSS custom properties (variables) for scalable and maintainable theme management.
Architecture:
ThemeProvider (Context) → localStorage (persistence) → CSS Variables (styling) → System Preference Detection
Key Features:
- Theme toggle button in navbar with moon/sun icons
- Automatic detection of system preference (prefers-color-scheme)
- Persist user preference in localStorage
- Smooth transitions between themes (0.3s ease)
- WCAG AA compliant color contrasts
- No additional npm packages required
Technical Implementation
Step 1: Create Theme Context
Create src/context/ThemeContext.js:
import React, { createContext, useContext, useEffect, useState } from 'react';
const ThemeContext = createContext();
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within ThemeProvider');
return context;
};
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState(() => {
const savedTheme = localStorage.getItem('acm-fun-theme');
if (savedTheme) return savedTheme;
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('acm-fun-theme', theme);
}, [theme]);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e) => {
if (!localStorage.getItem('acm-fun-theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};Step 2: Define CSS Variables
Create src/styles/themes.css:
/* Light Theme */
:root, [data-theme="light"] {
--color-primary: #667eea;
--color-bg-primary: #ffffff;
--color-bg-secondary: #f7fafc;
--color-text-primary: #1a202c;
--color-text-secondary: #4a5568;
--color-border-primary: #e2e8f0;
--color-card-bg: #ffffff;
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
/* Dark Theme */
[data-theme="dark"] {
--color-primary: #7c3aed;
--color-bg-primary: #0f0f23;
--color-bg-secondary: #1a1a2e;
--color-text-primary: #f7fafc;
--color-text-secondary: #e2e8f0;
--color-border-primary: #2d3748;
--color-card-bg: #1e1e3f;
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
}
/* Smooth Transitions */
* {
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}Step 3: Create Theme Toggle Component
Create src/components/common/ThemeToggle.js and ThemeToggle.css:
import React from 'react';
import { useTheme } from '../../context/ThemeContext';
import './ThemeToggle.css';
export const ThemeToggle = () => {
const { theme, toggleTheme } = useTheme();
return (
<button
className="theme-toggle"
onClick={toggleTheme}
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
>
{theme === 'light' ? '🌙' : '☀️'}
</button>
);
};Step 4: Integrate into App
Update src/App.js:
import { ThemeProvider } from './context/ThemeContext';
import './styles/themes.css';
function App() {
return (
<ThemeProvider>
<div className="App">
<Navbar/>
{/* ... rest of app */}
</div>
</ThemeProvider>
);
}Update src/components/common/Navbar.js to include <ThemeToggle />.
Step 5: Migrate CSS Files
Replace all hardcoded colors with CSS variables. Example:
Before:
.game-card-root {
background: #ffffff;
color: #1a202c;
border: 1px solid #e2e8f0;
}After:
.game-card-root {
background: var(--color-card-bg);
color: var(--color-text-primary);
border: 1px solid var(--color-border-primary);
}Component Migration Checklist
Layout (Priority 1):
-
src/App.css -
src/components/common/Navbar.js+ CSS
Pages (Priority 2):
- Home, Games, Activities, Favorites pages + CSS
Cards (Priority 3):
- GameCard + CSS
- ActivityCard + CSS
Games (Priority 4):
- TicTacToe, MagicSquares, Wordle, GuessFlag, Jitter, SimonSays, ReactionTime
- MemeCaptionMaker, GKQuiz, FlappyBird, WordScramble, DontClickBomb, TypingTest
Activities (Priority 5):
- RandomQuote, RandomAnimeQuote, RandomJoke, RandomMemes, FortuneCard
- SearchWord, Calculator, DogHttpCode, CatHttpCode, RandomIdentity
Testing Requirements
Manual Testing
- Toggle switches between themes instantly
- Theme persists on page reload
- Respects system dark mode preference on first visit
- All text readable in both modes
- No color contrast issues
- Works on Chrome, Firefox, Safari, Edge
- Keyboard accessible (Tab + Enter/Space)
- Mobile-friendly toggle button
Automated Tests
describe('ThemeContext', () => {
test('toggles theme from light to dark', () => {
const { result } = renderHook(() => useTheme(), { wrapper: ThemeProvider });
act(() => result.current.toggleTheme());
expect(result.current.theme).toBe('dark');
});
test('persists theme to localStorage', () => {
const { result } = renderHook(() => useTheme(), { wrapper: ThemeProvider });
act(() => result.current.toggleTheme());
expect(localStorage.getItem('acm-fun-theme')).toBe('dark');
});
});Accessibility Requirements (WCAG 2.1 AA)
- Text contrast ratio >= 4.5:1 (normal text)
- Text contrast ratio >= 3:1 (large text, 18pt+)
- Interactive elements contrast >= 3:1
- Theme toggle keyboard accessible
- Theme toggle has descriptive aria-label
- Focus indicators clearly visible in both themes
- Respects prefers-reduced-motion for animations
Success Criteria
Must Have:
- Functional theme toggle in navbar
- All 30+ CSS files use CSS variables
- Theme preference persists in localStorage
- System preference detection working
- All components visible in both themes
- WCAG AA compliant colors
- No performance degradation
- All existing tests pass
Should Have:
- Smooth transition animations
- Mobile-optimized toggle
- Documentation updated
Browser Support
Minimum Requirements:
- Chrome 49+, Firefox 31+, Safari 9.1+, Edge 15+ (CSS Variables support)
- Mobile browsers: Last 2 versions
Fallback: For browsers without CSS Variables support (< 1% users), display in light mode only.
Performance Considerations
Targets:
- Theme toggle response time < 100ms
- No degradation in Lighthouse scores
- No layout shift during theme change
- Minimal re-renders (only theme-dependent components)
Color Palette
Light Theme:
- Primary: #667eea, Background: #ffffff, Text: #1a202c
Dark Theme:
- Primary: #7c3aed, Background: #0f0f23, Text: #f7fafc
All color combinations meet WCAG AA standards (contrast ratios 4.5:1+).
Known Limitations
- Some game icons may need dark mode variants
- External API content may not respect theme
- Canvas-based games may need special handling
- Third-party components (slick carousel) need custom styling
References
- CSS Custom Properties (MDN)
- React Context API
- prefers-color-scheme
- CSS-Tricks: Complete Guide to Dark Mode
- WebAIM Contrast Checker
Need Help Implementing This?
I can assist with the implementation in the following ways:
Option 1: Full Implementation Support
I can help you implement the entire feature step-by-step:
- Set up the theme infrastructure
- Create all necessary files with complete code
- Migrate CSS files to use variables
- Write tests
- Debug any issues
Option 2: Guided Implementation
I can guide you through the implementation:
- Answer questions about specific steps
- Review your code and provide feedback
- Help troubleshoot issues
- Suggest best practices
Option 3: Specific Component Help
Need help with specific parts:
- CSS variable naming conventions
- Color palette selection for accessibility
- Testing strategies
- Performance optimization
- Browser compatibility fixes
Getting Started
If you'd like assistance, I can:
- Create the initial ThemeContext file for you
- Set up the CSS variables with a color palette
- Build the ThemeToggle component
- Show you how to migrate one component as an example
- Help integrate it into your App.js
Just let me know which approach works best for you, or if you'd like me to start with creating the foundation files!
Related Issues
- Issue Change link embed information. #16: Color Contrast Audit
- Issue TicTacToe Implemented #27: Toast Notification System (should support themes)
- Issue Arceen: Improve UI of Random quote activity #52: Mobile Responsiveness