Theming
Relevant source files
The following files were used as context for generating this wiki page:The application implements a dynamic theming system that allows users to switch between a light and a dark mode. This system is built using React's Context API for state management, localStorage for persistence, and Tailwind CSS's class-based strategy for applying styles. The theme state is managed globally and affects the entire application's appearance, from background colors to component-specific styles.
The core of the system is the ThemeContext, which provides the current theme and a function to toggle it. This context manipulates the DOM by adding or removing a dark class on the root <html> element, which in turn triggers conditional styles defined in both global CSS and Tailwind CSS utility classes.
Architecture
The theming architecture is composed of three main parts: a React Context for state management, a Tailwind CSS configuration to enable class-based dark mode, and global CSS rules for base theme colors.
This diagram illustrates the high-level architecture of the theming system.
Key Components
| File | Responsibility |
|---|---|
frontend/src/context/ThemeContext.jsx | Defines the React context, manages theme state (light/dark), handles persistence to localStorage, and applies the .dark class to the <html> element. |
frontend/tailwind.config.js | Configures Tailwind CSS to use the class strategy for dark mode, enabling styles prefixed with dark:. |
frontend/src/index.js | Wraps the entire application with ThemeProvider to make the theme state globally available. |
frontend/src/index.css | Contains global CSS rules that define the base background and text colors for both light and dark themes. |
frontend/src/components/common/Sidebar.js | An example of a component that consumes the theme context to apply conditional styles. |
Sources: frontend/src/context/ThemeContext.jsx, frontend/tailwind.config.js, frontend/src/index.js, frontend/src/index.css, frontend/src/components/common/Sidebar.js
ThemeContext and ThemeProvider
The ThemeContext is the central piece of the theming system. It is created using React.createContext and is managed by the ThemeProvider component.
Sources: frontend/src/context/ThemeContext.jsx:10
State Management
The ThemeProvider component maintains the current theme state ('light' or 'dark'). It initializes the theme from localStorage if a value is present, otherwise defaulting to the user's system preference via window.matchMedia('(prefers-color-scheme: dark)').
Sources: frontend/src/context/ThemeContext.jsx:13-16
DOM Manipulation and Persistence
A useEffect hook within ThemeProvider is responsible for two key side effects:
- DOM Manipulation: It adds or removes the
darkclass from the root<html>element based on the current theme state. - Persistence: It saves the current theme to
localStoragewhenever it changes.
// frontend/src/context/ThemeContext.jsx:23-29
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove(theme === "dark" ? "light" : "dark");
root.classList.add(theme);
localStorage.setItem("theme", theme);
}, [theme]);
Theme Toggling Flow
The toggleTheme function provided by the context allows any component to switch the theme.
This sequence diagram shows what happens when a user action triggers toggleTheme.
Styling Configuration
Tailwind CSS
The project configures Tailwind CSS to use a class-based dark mode. This is achieved by setting darkMode: "class" in the configuration file. This tells Tailwind to apply styles prefixed with dark: (e.g., dark:bg-zinc-900) whenever the dark class is present on an ancestor element.
// frontend/tailwind.config.js:2-7
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: { extend: {} },
plugins: [],
};
Global CSS
Global styles in frontend/src/index.css define the base colors for the application. A specific rule targets the body element when the .dark class is applied to an ancestor (in this case, <html>), changing the background and text colors for the entire app.
/* frontend/src/index.css:6-9 */
body {
@apply bg-white text-zinc-900 transition-colors duration-300;
}
/* Khi bật dark mode, tự đổi nền và màu chữ toàn ứng dụng */
/* frontend/src/index.css:11-13 */
.dark body {
@apply bg-zinc-900 text-zinc-100 transition-colors duration-300;
}
This ensures a consistent look and feel when switching themes. The transition-colors utility provides a smooth animation during the theme change.
Integration and Usage
Application Root
The ThemeProvider is wrapped around the App component in frontend/src/index.js, ensuring that the theme context is available to every component in the application tree.
// frontend/src/index.js:18-22
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);
Component-Level Usage
Components can access the theme state using the useTheme custom hook. This allows for conditional rendering of styles or components based on the active theme.
The Sidebar component is a clear example. It uses the theme variable to conditionally apply different background colors and box shadows for the active menu item highlight.
// frontend/src/components/common/Sidebar.js:58-66
<div
className={`absolute left-[13px] w-[189px] h-[44px] rounded-[14px]
${theme === "light" ? "bg-[#ccf067]" : "bg-emerald-500/25"}`}
style={{
top: `${highlightTop}px`,
transition: `top ${TRANS_DUR}ms ease`,
boxShadow:
theme === "light"
? "1px 5px 18px rgba(0,0,0,0.06)"
: "0 6px 18px rgba(16,24,32,0.6)",
}}
/>
Similarly, the main aside element of the sidebar also changes its background and text color based on the theme.
// frontend/src/components/common/Sidebar.js:47-50
<aside
className={`w-[216px] h-[700px] rounded-[25px] shadow relative flex flex-col transition-colors duration-300
${theme === "light" ? "bg-white text-black" : "bg-zinc-900 text-zinc-200"}`}
>
This pattern is repeated across various components like AdminSetting and AirQualityIndexPage to ensure a fully themed user interface.
Sources: frontend/src/components/common/Sidebar.js:49, frontend/src/components/common/Sidebar.js:59, frontend/src/components/common/Setting.js:8, frontend/src/pages/AirQualityIndexPage.jsx:100