Skip to content

ThemeToggle

Paulo Gomes da Cruz Junior edited this page Apr 13, 2026 · 1 revision

ThemeToggle

The ThemeToggle component provides an animated toggle switch that allows users to switch between light and dark themes. It features a sliding circle animation with theme-appropriate icons.

Props

This component does not accept any props. It manages its own state through the useTheme context hook.

Usage

import ThemeToggle from "@/components/Layout/ThemeToggle";

<ThemeToggle />;

Features

Theme States

The component supports two theme modes:

  • Light Mode (g10): Displays a sun icon (LightFilled)
  • Dark Mode: Displays a moon icon (AsleepFilled)

Interactive Controls

  • Click: Toggle between themes
  • Keyboard: Press Enter or Space to toggle
  • Accessibility: Proper ARIA labels for screen readers

Animation

The toggle features a smooth sliding animation:

  • Circle slides left for light mode
  • Circle slides right for dark mode
  • Transition duration: 0.3s with ease-in-out timing

Theme Integration

The component uses the useTheme hook to:

  • Access the current theme state
  • Toggle between theme modes
  • Persist theme preferences
const { theme, toggleTheme } = useTheme();

Implementation Details

Class Name Logic

The component uses the current theme to determine the state class:

className={`theme-toggle ${theme !== 'g10' ? 'on' : 'off'}`}
  • When theme === 'g10' (light mode): class is "theme-toggle off"
  • When theme !== 'g10' (dark mode / 'g100'): class is "theme-toggle on"

Visual Design

Light Mode (Off State - className="off")

  • Background: White
  • Circle position: Left (translateX(0))
  • Circle background: Brand color (bcgov-background-brand)
  • Icon: Sun/light filled icon with white color
  • Icon path fill: currentColor (white)

Dark Mode (On State - className="on")

  • Background: Dark gray (colors.gray-100)
  • Circle position: Right (translateX(24px))
  • Circle background: White
  • Icon: Moon/asleep filled icon with dark gray color
  • Icon path fill: currentColor (dark gray)

Styling

Base Styles

.theme-toggle {
  width: 3rem;
  height: 1.5rem;
  border-radius: 1rem;
  display: flex;
  align-items: center;
  padding: 0.188rem;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

Icon Styling

.icon {
  width: 3.125rem;
  height: 3.125rem;
  padding: 1px;
}

Icons are oversized relative to the circle container to ensure proper visual coverage and fill.

Circle Animation

.circle {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  transition:
    transform 0.3s,
    background-color 0.3s;
  background-color: var(--#{variables.$bcgov-prefix}-background-brand);
}

State Classes

&.off {
  background-color: colors.$white;
  .circle {
    transform: translateX(0);
  }
  .icon {
    color: colors.$white;
  }
  .icon path {
    fill: currentColor;
  }
}

&.on {
  background-color: colors.$gray-100;
  .circle {
    transform: translateX(24px);
    background-color: colors.$white;
  }
  .icon {
    color: colors.$gray-100;
  }
  .icon path {
    fill: currentColor;
  }
}

Design Tokens

  • Light mode background: colors.$white (Carbon design token)
  • Dark mode background: colors.$gray-100 (Carbon design token)
  • Circle brand color: var(--bcgov-background-brand) (BC Government design token)
  • Icon size: 3.125rem × 3.125rem

Accessibility

ARIA Labels

  • Light mode icon: aria-label="light mode"
  • Dark mode icon: aria-label="dark mode"

Keyboard Support

onKeyDown={(e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    toggleTheme();
  }
}}

Focus Management

  • Component has tabIndex={0} for keyboard navigation
  • Role is set to button for proper semantics

Use Cases

  1. Header Actions: Typically placed in the global header bar
  2. User Preferences: Part of the user settings interface
  3. Accessibility: Helps users with visual preferences or requirements

Dependencies

Required Context

  • ThemeProvider: Must be wrapped in ThemeProvider to access theme state

Carbon Icons

  • LightFilled: Sun icon for light mode
  • AsleepFilled: Moon icon for dark mode

Integration Example

import { ThemeProvider } from "@/context/theme/ThemeProvider";
import ThemeToggle from "@/components/Layout/ThemeToggle";

function App() {
  return (
    <ThemeProvider>
      <Header>
        <HeaderGlobalAction>
          <ThemeToggle />
        </HeaderGlobalAction>
      </Header>
    </ThemeProvider>
  );
}

Related Components

  • LayoutHeaderGlobalBar: Contains ThemeToggle as a global action
  • LayoutHeader: Parent component that displays the header bar

Best Practices

  1. Single Instance: Use only one ThemeToggle per application
  2. Theme Provider: Always wrap in ThemeProvider context
  3. Persistent Placement: Keep in a consistent location (typically header)
  4. Accessibility: Ensure keyboard navigation and screen reader support

Notes

  • Theme changes are persisted across sessions
  • The component follows Carbon Design System patterns
  • Icons are properly sized and colored based on current theme
  • Smooth transitions enhance user experience

Waste Plus Wiki

Home

Architecture

Codes

Decisions

Knowledge Base

Development

Changes

Clone this wiki locally