Skip to main content

ThemeSwitch

What this component provides:

  • A theme toggle built on GlassSwitch
  • Visual feedback with Moon/Sun icons via lucide-react
  • Accessibility-friendly role="switch" with clear aria-labels
  • Automatic theme persistence via the useTheme hook

Dependencies​

  • lucide-react - For Moon and Sun icons
  • @hooks/useTheme - Custom hook for theme management
  • GlassSwitch - Shared switch component (handles role/aria-checked and styles)

Basic usage​

import ThemeSwitch from "@components/ThemeSwitch";

export default function Navigation() {
return (
<nav>
<h1>My App</h1>
<ThemeSwitch />
</nav>
);
}

The component renders a GlassSwitch that:

  • Shows a Moon icon when the current theme is light (aria-label: "switch to dark mode")
  • Shows a Sun icon when the current theme is dark (aria-label: "switch to light mode")
  • Persists the user's theme preference via useTheme

Props​

This component accepts no props. It's a self-contained theme toggle that manages its own state through the useTheme hook.

Styling​

ThemeSwitch delegates styling to GlassSwitch, which provides glass-morphism visuals, focus states, and thumb animations. To customize appearance, wrap ThemeSwitch in your own container and style the wrapper or fork GlassSwitch for deeper changes.

Accessibility​

ThemeSwitch uses GlassSwitch, which implements accessible switch semantics:

FeatureImplementation
Rolerole="switch" with aria-checked reflecting current theme
ARIA labelaria-label="switch to dark mode" or "switch to light mode"
Button typetype="button" to avoid form submission
Keyboard supportTab to focus; Enter/Space toggles
Focus indicatorVisible focus ring via :focus-visible with 2px outline and box-shadow (WCAG 2.4.7)
Icon semanticsIcons are decorative; meaning conveyed through the aria-label

Examples​

In a navigation bar​

import ThemeSwitch from "@components/ThemeSwitch";
import styles from "./Nav.module.css";

export default function NavBar() {
return (
<nav className={styles.nav}>
<div className={styles.logo}>Img2Num</div>
<div className={styles.actions}>
<ThemeSwitch />
</div>
</nav>
);
}

In a settings panel​

import ThemeSwitch from "@components/ThemeSwitch";

export default function Settings() {
return (
<div className="settings-panel">
<h2>Preferences</h2>
<div className="setting-row">
<label>Theme</label>
<ThemeSwitch />
</div>
</div>
);
}

Checking current theme alongside the switch​

import ThemeSwitch from "@components/ThemeSwitch";
import { useTheme } from "@hooks/useTheme";

export default function ThemeControl() {
const { theme } = useTheme();

return (
<div>
<p>
Current theme: <strong>{theme}</strong>
</p>
<ThemeSwitch />
</div>
);
}

How it works​

The component:

  1. Calls useTheme() to get the current theme and toggle function
  2. Renders a <button> element with role="switch" and an onChange handler that calls toggleTheme()
  3. Conditionally displays a Sun icon (dark mode) or Moon icon (light mode) based on current theme
  4. The useTheme hook handles:
    • Reading the initial theme from localStorage or system preferences
    • Applying theme classes to document.documentElement
    • Saving theme changes to localStorage

Implementation​

ThemeSwitch.jsx
import { Moon, Sun } from "lucide-react";
import GlassSwitch from "./GlassSwitch";
import { useTheme } from "@hooks/useTheme";

// Theme toggle built on top of GlassSwitch
export default function ThemeSwitch() {
const { theme, toggleTheme } = useTheme();
const isDark = theme === "dark";

return <GlassSwitch isOn={isDark} onChange={toggleTheme} thumbContent={isDark ? <Sun /> : <Moon />} ariaLabel={`switch to ${isDark ? "light" : "dark"} mode`} />;
}

Visual behavior​

Current ThemeIcon Displayedaria-labelNext Theme on Toggle
Light🌙 Moon"switch to dark mode"Dark
Dark☀️ Sun"switch to light mode"Light

Testing​

The component is fully tested. Key test scenarios include:

  • Rendering with correct button type and aria-label
  • Displaying the correct icon based on theme
  • Calling toggleTheme when clicked
  • Integration with the useTheme hook
  • Keyboard accessibility

See the test documentation for details.

  • useTheme Hook - The underlying hook that powers theme switching
  • CSS Theme Variables - CSS variables that change with the theme
  • NavBar Component - Example usage in the navigation bar