Theming (Dark/Light Mode)
Relevant source files
The following files were used as context for generating this wiki page:The portfolio application features a "Dark Mode Intelligence" system, which provides a persistent, system-aware theming engine for switching between light and dark modes. This system is designed to be efficient, prevent the "flash of incorrect theme" (FOUC), and leverage modern browser APIs for a smooth user experience.
The core of the theming system relies on a combination of React Context for state management, CSS Custom Properties (variables) for styling, and the browser's localStorage for persistence. The theme is applied globally by setting a data-theme attribute on the <html> element.
Sources: README.md, src/app/providers/ThemeProvider/ThemeContext.tsx:24-27
Architecture Overview
The theming architecture is composed of three main parts:
ThemeProvider: A React context provider that manages the theme state (lightordark).ThemeToggle: A UI component that allows the user to switch between themes.- CSS Stylesheet: A central CSS file (
index.css) that defines theme-specific styles using CSS variables controlled by thedata-themeattribute.
This diagram illustrates the high-level relationship between these components.
CSS Variable Strategy
Theming is implemented using CSS Custom Properties defined in src/app/index.css. A set of semantic variables for colors (backgrounds, foregrounds, borders, etc.) is defined. The theme is switched by changing the values of these variables based on the data-theme attribute on the <html> element.
There are two primary selectors that define the theme palettes:
html[data-theme="light"]html[data-theme="dark"]
These selectors override the default values of the CSS variables, causing the entire UI to update its color scheme. Tailwind CSS is configured to use these variables, making the theming system compatible with the utility-first framework.
Sources: src/app/index.css:5-103
Key Theme Variables
The following table lists some of the core semantic variables and their purpose.
| Variable Name | Description | Light Theme Value | Dark Theme Value |
|---|---|---|---|
--bg-default | The main background color of the page. | #ffffff | #121212 |
--bg-paper | Background for card-like elements. | #f4f4f5 | #1e1e20 |
--fg-primary | Primary text color. | #18181b | #f4f4f5 |
--fg-secondary | Secondary text color for less emphasis. | #52525b | #a1a1aa |
--border-default | Default border color for elements. | #e4e4e7 | #27272a |
--primary | The primary brand/action color. | #2563eb | #3b82f6 |
Sources: src/app/index.css:15-39, 44-70
A default theme-color meta tag is set to the dark theme's background color in the main HTML file to control the browser UI color.
Sources: index.html:16
Theme State Management
State management is handled by the ThemeProvider component, which uses React's Context API.
Sources: src/app/providers/ThemeProvider/ThemeContext.tsx
ThemeProvider
The ThemeProvider is responsible for:
- Initializing the theme state on application load.
- Providing the current theme and a
toggleThemefunction to its children viaThemeContext. - Updating the
data-themeattribute on the<html>element when the theme changes. - Persisting the user's selected theme to
localStorage.
The ThemeProvider is wrapped around the entire application in App.tsx to ensure the context is available everywhere.
Sources: src/app/App.tsx:25
Theme Initialization Logic
On initial load, the ThemeProvider determines the theme with the following priority:
- Check
localStorage: It first looks for athemekey inlocalStorage. If a valid value (lightordark) is found, it's used. This respects the user's last explicit choice. - Check System Preference: If
localStorageis empty, it useswindow.matchMediato check the user's operating system preference (prefers-color-scheme). - Default: If neither of the above is available, it defaults to
dark.
This flow is visualized in the diagram below.
ThemeContext
The context provides a simple interface for consuming components to interact with the theming system.
| Property | Type | Description |
|---|---|---|
theme | "light" | "dark" | The current active theme. |
toggleTheme | () => void | A function to switch between light and dark themes. |
Sources: src/app/providers/ThemeProvider/ThemeContext.tsx:6-9
Theme Toggling
The ThemeToggle component is the primary user-facing control for changing the theme. It is used within the site's Footer.
Sources: src/widgets/Footer/Footer.tsx:380
ThemeToggle Component
This component consumes the ThemeContext to get the current theme and the toggleTheme function. It renders a custom SVG-based toggle switch that animates between a sun and a moon icon.
Sources: src/features/theme/ui/ThemeToggle.tsx:5-12, 69-137
View Transition API Integration
The ThemeToggle component enhances the theme change with a "ripple" effect using the experimental View Transitions API.
When the toggle is clicked, the handleToggle function executes the following steps:
- Checks if
document.startViewTransitionis supported by the browser. If not, it callstoggleThemeimmediately for a standard theme change. - If supported, it wraps the
toggleTheme()call insidedocument.startViewTransition(). This captures the DOM state before and after the theme change. - It calculates the distance from the click coordinates to the furthest corner of the viewport.
- It uses the Web Animations API (
document.documentElement.animate) to animate aclip-pathon the::view-transition-new(root)pseudo-element, creating a circular reveal (ripple) effect originating from the toggle button's position.
The sequence diagram below details this interaction.
Conclusion
The theming system in this portfolio is a robust and modern implementation that prioritizes user experience. By combining CSS variables for flexibility, React Context for clean state management, and localStorage for persistence, it provides a seamless dark/light mode feature. The progressive enhancement with the View Transitions API demonstrates a commitment to leveraging cutting-edge web platform features to create polished and delightful interactions.