Skip to main content

Overview

The ThemeContext provides centralized theme management for the CicloVital application. It automatically applies theme classes to the document body and persists user preferences to localStorage.

Context Shape

interface ThemeContextValue {
  theme: string;
  setTheme: (theme: string) => void;
}

Properties

theme
string
The current theme identifier. Defaults to "theme-dark".Common values:
  • "theme-dark" - Dark theme
  • "theme-light" - Light theme
setTheme
function
Function to update the current theme.
(theme: string) => void

Provider Component

ThemeProvider

Wraps your application to provide theme context to all child components.
import { ThemeProvider } from './contexts/ThemeProvider';

function App() {
  return (
    <ThemeProvider>
      {/* Your app components */}
    </ThemeProvider>
  );
}

Features

  • Automatic DOM Updates: Theme class is automatically applied to document.body
  • Persistence: Theme preference is saved to localStorage
  • Session Recovery: Loads theme preference from localStorage on initialization
  • Safe Storage: Includes error handling for localStorage operations
  • Default Theme: Falls back to "theme-dark" if no preference exists

Usage

Consuming the Context

import { useContext } from 'react';
import ThemeContext from './contexts/ThemeContext';

function ThemeDisplay() {
  const { theme } = useContext(ThemeContext);

  return <p>Current theme: {theme}</p>;
}

Theme Toggle

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

  const toggleTheme = () => {
    const newTheme = theme === 'theme-dark' ? 'theme-light' : 'theme-dark';
    setTheme(newTheme);
  };

  return (
    <button onClick={toggleTheme}>
      Switch to {theme === 'theme-dark' ? 'Light' : 'Dark'} Mode
    </button>
  );
}

Theme Selector

function ThemeSelector() {
  const { theme, setTheme } = useContext(ThemeContext);

  const themes = [
    { id: 'theme-dark', name: 'Dark' },
    { id: 'theme-light', name: 'Light' },
    { id: 'theme-blue', name: 'Blue' },
    { id: 'theme-green', name: 'Green' },
  ];

  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value)}>
      {themes.map(t => (
        <option key={t.id} value={t.id}>
          {t.name}
        </option>
      ))}
    </select>
  );
}

Conditional Styling

function Header() {
  const { theme } = useContext(ThemeContext);

  return (
    <header className={`header ${theme}`}>
      <h1>CicloVital</h1>
      {theme === 'theme-dark' && <span>๐ŸŒ™</span>}
      {theme === 'theme-light' && <span>โ˜€๏ธ</span>}
    </header>
  );
}

Implementation Details

Source Files

  • src/contexts/ThemeContext.js - Context definition
  • src/contexts/ThemeProvider.jsx - Provider implementation

localStorage Key

The context stores theme preference under the key "theme" in localStorage.

DOM Integration

The provider automatically applies the theme as a class name to document.body:
useEffect(() => {
  document.body.className = theme;
}, [theme]);
This allows CSS to target the theme:
body.theme-dark {
  background-color: #1a1a1a;
  color: #ffffff;
}

body.theme-light {
  background-color: #ffffff;
  color: #000000;
}

Initial State

On mount, the provider attempts to load the theme from localStorage. If no preference exists or parsing fails, it defaults to "theme-dark".
const [theme, setTheme] = useState(() => safeGet("theme", "theme-dark"));

CSS Integration

Theme-specific Styles

/* Dark theme */
body.theme-dark {
  --bg-primary: #1a1a1a;
  --text-primary: #ffffff;
  --accent: #4a9eff;
}

/* Light theme */
body.theme-light {
  --bg-primary: #ffffff;
  --text-primary: #000000;
  --accent: #0066cc;
}

/* Use CSS variables in components */
.card {
  background: var(--bg-primary);
  color: var(--text-primary);
}

Best Practices

Use CSS custom properties (variables) with theme classes for maintainable, scalable theming.
The theme class replaces the entire document.body.className. If you need to preserve other classes, modify the implementation to append rather than replace.

Advanced Usage

Prefers Color Scheme

Respect userโ€™s system preference:
import { useContext, useEffect } from 'react';
import ThemeContext from './contexts/ThemeContext';

function App() {
  const { theme, setTheme } = useContext(ThemeContext);

  useEffect(() => {
    // Only set on first load if no preference exists
    const storedTheme = localStorage.getItem('theme');
    if (!storedTheme) {
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      setTheme(prefersDark ? 'theme-dark' : 'theme-light');
    }
  }, []);

  return <div>{/* Your app */}</div>;
}

Theme Transitions

Add smooth transitions when theme changes:
body {
  transition: background-color 0.3s ease, color 0.3s ease;
}

Build docs developers (and LLMs) love