Theming (Dark/Light Mode)
Relevant source files
The following files were used as context for generating this wiki page:
The Quant application features a user-selectable dark and light mode to ensure a comfortable user experience in various lighting environments. This system is designed to be persistent across sessions and can also respect the user's operating system preference. The implementation relies on a combination of CSS custom properties (variables), a dedicated React hook for state management, and direct integration into key UI components.
The theming feature is explicitly listed as a user experience enhancement, highlighting its importance in the project. The core mechanism involves toggling a dark class on the root <html> element, which in turn activates a different set of CSS variables to restyle the entire application.
Sources: README.md:81-82
Architecture Overview
The theming system is built on three main pillars:
- CSS Custom Properties: A foundational set of color and style variables are defined for the light theme, with overrides provided for the dark theme.
- React Hook (
useTheme): A custom hook that encapsulates the logic for reading, updating, and persisting the theme state. - Component-Level Implementation: React components utilize the hook and CSS classes to apply the theme, toggle it, and display theme-specific assets.
This diagram shows the high-level interaction between these parts.
CSS Variable-based Theming
The entire color scheme of the application is controlled by CSS custom properties defined in src/styles/global.css.
Variable Definitions
- Light Theme (Default): Variables are defined within the
:rootselector, making them the default for the application. - Dark Theme: A complete set of overriding variables is defined within the
.darkclass selector. When the<html>element has the classdark, these variables take precedence.
The colors are defined using the oklch color function for modern, perceptually uniform color spaces.
Sources: src/styles/global.css:48-89, src/styles/global.css:91-132
Key Color Variables
The following table shows a comparison of some of the primary color variables for light and dark themes.
| Variable Name | Light Theme Value (:root) | Dark Theme Value (.dark) | Description |
|---|---|---|---|
--background | oklch(1 0 0) (White) | oklch(0.141 0.005 285.823) (Near-black) | Main page background color. |
--foreground | oklch(0.141 0.005 285.823) (Near-black) | oklch(0.985 0 0) (Near-white) | Main text color. |
--card | oklch(1 0 0) (White) | oklch(0.21 0.006 285.885) (Dark gray) | Background color for card components. |
--primary | oklch(58.8% 0.158 241.966) (Blue) | oklch(74.6% 0.16 232.661) (Lighter blue) | Primary accent color for interactive elements. |
--border | oklch(0.92 0.004 286.32) (Light gray) | oklch(1 0 0 / 10%) (Transparent white) | Border color for components. |
--destructive | oklch(0.577 0.245 27.325) (Red) | oklch(0.704 0.191 22.216) (Lighter red) | Color for destructive actions. |
Sources: src/styles/global.css:49-89, src/styles/global.css:92-132
Additionally, constants for light and dark background colors are defined for specific use cases, such as chart exports.
// src/lib/constants.ts
export const DARK_THEME_BGCOLOR = `#18181B`;
export const LIGHT_THEME_BGCOLOR = `#ffffff`;
Sources: src/lib/constants.ts:31-32
Theme Management Hook: useTheme
The core logic for managing the theme is encapsulated in the useTheme custom hook, located in src/hooks/use-theme.ts.
Hook Interface
The hook returns an object containing the current theme and a function to update it.
// src/hooks/use-theme.ts
export interface UseThemeResult {
theme: `light` | `dark`
setTheme: (theme: `light` | `dark`) => void
}
Sources: src/hooks/use-theme.ts:7-10
Initialization Logic
On initialization, the Header component determines the theme by checking sources in a specific order of priority.
This flow is visualized in the sequence diagram below.
This diagram illustrates the theme initialization process within the Header component.
Sources: src/components/Header.tsx:40-44
Theme Toggling
The toggleTheme function in the Header component handles switching between light and dark modes. The useTheme hook provides the underlying mechanism to persist this change.
The process is as follows:
- The user clicks the theme toggle button.
- The
toggleThemefunction inHeader.tsxis called. - It determines the
newTheme('dark'if current is'light', and vice-versa). - It updates its local state via
setTheme. - It saves the
newThemetolocalStorage.setItem('theme', newTheme). - It toggles the
darkclass ondocument.documentElement.
Sources: src/components/Header.tsx:46-51, src/hooks/use-theme.ts:56-59
This flow is shown in the diagram below.
This diagram shows the sequence of events when a user toggles the theme.
DOM Observation
The useTheme hook also uses a MutationObserver to listen for changes to the class attribute of the <html> element. This ensures that if the theme is changed by any other means, the hook's internal state (theme) remains synchronized with the DOM.
// src/hooks/use-theme.ts
useEffect(() => {
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains(`dark`);
setTheme(isDark ? `dark` : `light`);
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: [ `class` ],
});
return () => observer.disconnect();
}, []);
Sources: src/hooks/use-theme.ts:29-40
Component-Level Integration
Theming is integrated throughout the application's components.
Header Component
The Header component is the primary user-facing control for the theme.
- It contains the theme toggle button, which displays a
MoonorSunicon based on the current theme. - It conditionally renders different logos for light and dark modes using the
<picture>element and Tailwind'sdark:variant.
// src/components/Header.tsx
<a href="/" className="flex items-center gap-2">
<picture className="block dark:hidden" data-light-theme>
<img
src={logoBlack.src}
alt="CyberPath Quant Logo"
className="h-8 w-auto dark:hidden"
/>
</picture>
{/* Dark theme image */}
<picture className="hidden dark:block" data-dark-theme>
<img
src={logoWhite.src}
alt="CyberPath Quant Logo"
className="h-8 w-auto hidden dark:block"
/>
</picture>
</a>
Sources: src/components/Header.tsx:68-85
The toggle button itself is also present in the mobile navigation sheet.
Sources: src/components/Header.tsx:109-112, src/components/Header.tsx:173-179
Other Components
Other components consume the theme for specific purposes or rely on the global CSS variables.
CVSSGenericCalculator.tsx: Uses theuseThemehook to pass the current theme to theToastercomponent, ensuring notification pop-ups match the application's theme.Footer.tsx: Uses theme-aware Tailwind CSS classes likebg-backgroundandtext-muted-foregroundto adapt its appearance.README.md: The README file itself uses a<picture>element to display a theme-appropriate logo on GitHub.
Sources: src/components/CVSSGenericCalculator.tsx:280, src/components/Footer.tsx:11, README.md:2-6
Summary
The theming system in Quant is a robust and well-integrated feature. It provides a seamless user experience by respecting system preferences and persisting user choices. The architecture, centered around CSS variables and a dedicated React hook, is clean and scalable. The use of Tailwind's dark: utility classes and conditional rendering of assets in components like the Header demonstrates a practical and effective implementation of the theme across the application.