Skip to content

Implement Dark Mode Theme Support #135

@muhammadnoorulainroy

Description

@muhammadnoorulainroy

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

  1. Some game icons may need dark mode variants
  2. External API content may not respect theme
  3. Canvas-based games may need special handling
  4. Third-party components (slick carousel) need custom styling

References


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:

  1. Create the initial ThemeContext file for you
  2. Set up the CSS variables with a color palette
  3. Build the ThemeToggle component
  4. Show you how to migrate one component as an example
  5. 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


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions