You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.6 KiB
4.6 KiB
Light/Dark Theme System Design
Goal
Add a light theme option alongside the existing dark theme, with user-togglable switching via the Settings page. The toggle persists in localStorage and applies globally across all pages including login.
Architecture
CSS Variables + dark class on <html>
Standard shadcn/Tailwind pattern:
:rootdefines light theme CSS variables (default).darkclass on<html>overrides with dark theme valuesThemeProviderReact context manages the class toggle and localStorage sync- All components use semantic Tailwind utilities (
bg-background,text-foreground, etc.) instead of hardcodedbg-gray-900,text-white
Theme Provider
// src/contexts/ThemeContext.tsx
const ThemeContext = createContext<{ theme: 'light' | 'dark'; toggleTheme: () => void }>()
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(() => localStorage.getItem('settings:darkMode') !== 'false' ? 'dark' : 'light')
useEffect(() => {
document.documentElement.classList.toggle('dark', theme === 'dark')
localStorage.setItem('settings:darkMode', theme === 'dark' ? 'true' : 'false')
}, [theme])
const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark')
return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>
}
CSS Variable Definitions
In index.css, using Tailwind v4 @theme:
/* Light theme (default) */
:root {
--color-background: oklch(0.98 0 0); /* near white */
--color-foreground: oklch(0.15 0.01 240); /* near black */
--color-card: oklch(1.0 0 0); /* white */
--color-card-foreground: oklch(0.15 0.01 240);
--color-muted: oklch(0.96 0.005 240); /* gray-100 */
--color-muted-foreground: oklch(0.45 0.01 240); /* gray-500 */
--color-border: oklch(0.90 0.005 240); /* gray-200 */
--color-input: oklch(0.90 0.005 240);
--color-sidebar: oklch(0.97 0.003 240); /* gray-50 */
}
/* Dark theme */
.dark {
--color-background: oklch(0.10 0.01 240);
--color-foreground: oklch(0.98 0.005 240);
--color-card: oklch(0.13 0.01 240);
--color-card-foreground: oklch(0.98 0.005 240);
--color-muted: oklch(0.18 0.015 240);
--color-muted-foreground: oklch(0.60 0.02 240);
--color-border: oklch(0.22 0.015 240);
--color-input: oklch(0.22 0.015 240);
--color-sidebar: oklch(0.13 0.01 240);
}
Semantic Color Mapping
| Semantic Class | Dark Appearance | Light Appearance | Replaces |
|---|---|---|---|
bg-background |
oklch(0.10) near-black |
oklch(0.98) near-white |
bg-gray-950, body |
bg-card |
oklch(0.13) dark gray |
white | bg-gray-900/80 |
bg-muted |
oklch(0.18) medium dark |
oklch(0.96) light gray |
bg-gray-800/50 |
bg-sidebar |
oklch(0.13) dark |
oklch(0.97) near-white |
sidebar bg |
text-foreground |
white | near-black | text-white |
text-muted-foreground |
gray-400/500 | gray-500 | text-gray-400/500 |
border-border |
gray-800 | gray-200 | border-gray-800 |
What Stays the Same
- Accent colors: sky-500, blue-600 gradients work on both themes
- Status badge colors:
bg-red-500/20 text-red-400etc. work on both - Gradient buttons: primary/destructive button gradients are fine
- Focus rings: sky-500 focus rings work on both
Scope
Files to Create (2)
src/contexts/ThemeContext.tsx— ThemeProvider + useTheme hook
Files to Modify (~20)
src/index.css— Light/dark CSS variable sets, conditional body stylessrc/App.tsx— Wrap with ThemeProvidersrc/components/ui/Button.tsxsrc/components/ui/Card.tsxsrc/components/ui/Input.tsxsrc/components/ui/Modal.tsxsrc/components/ui/Table.tsxsrc/components/layout/Sidebar.tsxsrc/components/layout/Header.tsxsrc/components/layout/MainLayout.tsxsrc/pages/SettingsPage.tsx— Use ThemeContext instead of local togglesrc/pages/LoginPage.tsxsrc/pages/SSOCallbackPage.tsxsrc/pages/DashboardPage.tsxsrc/pages/UserManagementPage.tsxsrc/pages/FileManagementPage.tsxsrc/pages/MyPage.tsxsrc/pages/MenuManagementPage.tsxsrc/pages/RoleManagementPage.tsxsrc/pages/OrganizationManagementPage.tsx
Light Theme Visual Direction
- Clean & Professional — white/gray backgrounds, subtle borders, soft shadows
- Body: white background with very subtle blue-tinted grid pattern
- Cards: white with light gray borders and subtle shadow
- Sidebar: gray-50 background with clean borders
- Header: white/gray-50 with bottom border
- Text: gray-900 primary, gray-500 secondary
- Sky-500 accent color stays the same