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

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:

  1. :root defines light theme CSS variables (default)
  2. .dark class on <html> overrides with dark theme values
  3. ThemeProvider React context manages the class toggle and localStorage sync
  4. All components use semantic Tailwind utilities (bg-background, text-foreground, etc.) instead of hardcoded bg-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-400 etc. 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 styles
  • src/App.tsx — Wrap with ThemeProvider
  • src/components/ui/Button.tsx
  • src/components/ui/Card.tsx
  • src/components/ui/Input.tsx
  • src/components/ui/Modal.tsx
  • src/components/ui/Table.tsx
  • src/components/layout/Sidebar.tsx
  • src/components/layout/Header.tsx
  • src/components/layout/MainLayout.tsx
  • src/pages/SettingsPage.tsx — Use ThemeContext instead of local toggle
  • src/pages/LoginPage.tsx
  • src/pages/SSOCallbackPage.tsx
  • src/pages/DashboardPage.tsx
  • src/pages/UserManagementPage.tsx
  • src/pages/FileManagementPage.tsx
  • src/pages/MyPage.tsx
  • src/pages/MenuManagementPage.tsx
  • src/pages/RoleManagementPage.tsx
  • src/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