Feature: Light/Dark Mode
Relevant source files
The following files were used as context for generating this wiki page:
The application implements a user-configurable theme system that supports light, dark, and system-preference modes. This feature ensures a comfortable user experience across different lighting conditions and user preferences. The implementation relies on the next-themes library, React Context for state management, and a CSS variable-based styling strategy integrated with Tailwind CSS.
The core logic is encapsulated within a ThemeProvider, which manages the current theme state, persists it to localStorage, and dynamically applies a CSS class to the root HTML element. A ThemeToggle component provides the user interface for switching themes. The visual appearance is controlled by a set of CSS custom properties defined for both light and dark modes, which are then consumed by Tailwind's utility classes.
Architecture and Core Components
The theme system is built upon three main pillars: the ThemeProvider for state management, the ThemeToggle for user interaction, and a CSS/Tailwind configuration for styling.
ThemeProvider
The ThemeProvider is a React context provider that wraps the entire application. It is responsible for managing the theme state and applying it to the DOM. It is an implementation based on the next-themes package.
- State Management: It maintains the current theme (
"light","dark", or"system") in a React state. - Persistence: The selected theme is saved to
localStorageto persist the user's choice across sessions. The default storage key isvite-ui-theme. - DOM Manipulation: It adds or removes the
.darkor.lightclass from the root<html>element based on the current theme. For the"system"theme, it useswindow.matchMedia("(prefers-color-scheme: dark)")to determine and apply the appropriate class.
Sources: frontend/src/providers/theme-provider.tsx, frontend/package.json:44
Provider Configuration
The ThemeProvider accepts several props for configuration:
| Prop | Type | Default Value | Description |
|---|---|---|---|
children | React.ReactNode | - | The child components to be rendered within the provider's scope. |
defaultTheme | "dark" | "light" | "system" | "system" | The theme to use when no value is found in localStorage. |
storageKey | string | "vite-ui-theme" | The key used to store the theme value in localStorage. |
Sources: frontend/src/providers/theme-provider.tsx:8-12
ThemeToggle Component
This is a simple UI component that allows the user to switch between light and dark themes.
- It uses the
useThemehook to access the currentthemeand thesetThemefunction from theThemeProvidercontext. - It determines if the current effective theme is dark, considering the "system" setting.
- On click, it calls
setThemeto toggle between"light"and"dark". - It displays a
SunorMoonicon fromlucide-reactto represent the current mode and the action to be taken.
Sources: frontend/src/components/ThemeToggle.tsx
Styling and Configuration
The visual styling is handled by Tailwind CSS in conjunction with a set of CSS custom properties (variables).
Tailwind CSS Configuration
The tailwind.config.ts file enables class-based dark mode. This tells Tailwind to apply dark mode styles whenever the .dark class is present on a parent element (in this case, the <html> tag).
// frontend/tailwind.config.ts:5
export default {
darkMode: ["class"],
// ...
}
Sources: frontend/tailwind.config.ts:5
CSS Variables
The core of the theme styling is defined in index.css. It defines a complete set of color variables for the light theme within the :root selector and overrides them for the dark theme within the .dark selector. Components throughout the application use semantic color names (e.g., bg-background, text-foreground, border-primary/20) which resolve to the correct color based on the active theme.
This diagram illustrates how a component's style is resolved.
Key Color Variables
| Variable | Light Theme Value (:root) | Dark Theme Value (.dark) | Description |
|---|---|---|---|
--background | 210 50% 96% | 210 13% 9% | The main page background color. |
--foreground | 210 20% 12% | 210 40% 98% | The main page text color. |
--card | 210 40% 98% | 210 13% 9% | Background color for card components. |
--primary | 210 100% 47% | 210 100% 56% | Primary accent color for buttons, links. |
--destructive | 0 84.2% 60.2% | 0 62.8% 30.6% | Color for destructive actions (e.g., errors). |
--border | 210 25% 88% | 210 13% 15% | Default border color. |
--muted-foreground | 210 15% 45% | 215 20.2% 65.1% | Color for muted or secondary text. |
Sources: frontend/index.css:7-89
Data Flow and Logic
The process of changing a theme involves the user, the ThemeToggle component, the ThemeProvider, and the browser's DOM and localStorage.
This sequence diagram shows the interactions when a user clicks the theme toggle button.
The logic for determining which class to apply to the <html> element is handled within a useEffect hook in the ThemeProvider.
This flowchart illustrates the logic within the ThemeProvider.
Theme-Aware Components
Components are designed to be theme-aware by default through the use of semantic Tailwind CSS classes that map to the CSS variables. A good example is the Chart component, which has a specific mechanism to handle theme-based colors for data visualization.
The ChartContainer defines CSS variables scoped to the chart instance, which change based on whether the .dark class is present.
// frontend/src/components/ui/chart.tsx:78-96
// ...
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
// ...
This demonstrates how even complex components can leverage the same underlying .dark class strategy to adapt their appearance.
Sources: frontend/src/components/ui/chart.tsx:13-17, 78-96
Summary
The light/dark mode feature is a robust and well-integrated system. It leverages the next-themes library for the core logic, React Context for state propagation, and a powerful combination of Tailwind CSS's class-based variants and CSS custom properties for styling. This architecture ensures that theme changes are applied globally and efficiently, and that components can be developed to be theme-aware simply by using predefined semantic utility classes. The persistence of the theme choice in localStorage provides a consistent user experience across sessions.