Add Light/Dark Theme Support (Non-Invasive, Native, Stack-Agnostic)
Implement light/dark theme and a theme toggle in existing front-end codebases without changing styling paradigm or architecture.
You are working inside an existing production front-end codebase. Your objective is to implement light/dark theme capability and create a theme toggle component, while strictly adhering to the principles below.
🎯 Primary objectives
- Add light/dark theme capability.
- Persist theme preference using native browser mechanisms.
- Use the existing styling paradigm (do not introduce a new styling system).
- Use the existing component architecture (do not introduce a new framework or pattern).
- Avoid breaking changes.
- Keep the solution minimal and native.
- Prevent flash-of-incorrect-theme (FOUC) when possible.
- Avoid unnecessary external dependencies.
🚨 Hard constraints
You MUST
- Detect and use the existing styling approach (CSS, SCSS, CSS Modules, Tailwind, styled-components, emotion, inline styles, etc.).
- Extend existing infrastructure instead of replacing it.
- Avoid refactoring unrelated files.
- Avoid large-scale color rewrites unless absolutely required.
- Avoid introducing a global state library unless one already exists.
- Avoid introducing a new design system.
- Avoid adding heavy dependencies.
You MUST NOT
- Rewrite the styling paradigm.
- Introduce a completely new theming architecture.
- Break existing layouts or visual design.
- Replace static colors globally unless safe and minimal.
🧠 Architectural strategy
1. Detect existing styling strategy
Before implementation:
- Identify how styling is currently handled.
- Determine if CSS variables already exist.
- Determine whether a “dark mode” concept already partially exists.
- Identify how global styles are defined.
- Identify where root-level classes or attributes can be safely applied.
Adapt to what already exists.
2. Theming implementation strategy
Use the most native and minimal strategy possible based on the existing system. Prefer this order:
A. If CSS variables are already used
- Extend them to support dark variants.
- Define light and dark values at root scope.
- Toggle via
data-theme="dark"ORclass="dark"—whichever aligns with existing conventions.
B. If utility framework (e.g., Tailwind) is used
- Use the framework’s built-in dark mode support.
- Do NOT reconfigure the entire system.
- Enable dark mode using the existing configuration style.
C. If hard-coded colors dominate
- Introduce minimal semantic variables only where necessary.
- Do NOT refactor entire codebase.
- Only abstract what is required for light/dark switching.
💾 Theme persistence requirements
Use:
localStoragefor persistence.- Native browser APIs only.
- No heavy theme libraries unless the project already uses one.
Behavior:
- On first load: detect saved preference in
localStorage; if none exists, fall back toprefers-color-scheme. - Persist user selection.
- Apply theme immediately on load.
⚡ FOUC prevention
Implement an early inline script (before hydration if applicable) that:
- Reads
localStorage. - Applies the correct theme class or attribute to the root element.
- Runs before the UI renders.
This must be minimal, avoid blocking performance, and avoid hydration mismatch errors.
🧩 Theme toggle component
Create a toggle component using the existing component paradigm.
Requirements:
- Follow the existing component structure (functional, class-based, etc.).
- Use existing button or UI primitives if available.
- Do not introduce a new UI abstraction.
- Accessible:
aria-label, keyboard accessible, proper semantic element (button). - Visually minimal and consistent with current UI.
Behavior:
- Toggles between light and dark.
- Updates
localStorage. - Updates root theme attribute/class.
- Triggers UI update without page reload.
🏗 State management
If global state is required:
- Use existing context/provider if present.
- If none exists: create minimal theme context only if necessary; avoid Redux/Zustand/etc. unless already present.
Prefer root DOM attribute + reactive re-render over complex global state systems.
🔍 Edge cases to handle
- SSR environments (avoid accessing
windowdirectly without guards). - Hydration mismatches.
- Multiple mounts.
- Cross-tab sync (optional but ideal using
storageevent). - Embedded iframes (if relevant).
♿ Accessibility requirements
- Ensure sufficient contrast in both modes.
- Do not rely on color alone for meaning.
- Maintain focus styles.
- Preserve readability.
🧪 Testing requirements
After implementation, verify:
- No layout shifts occur.
- No hydration warnings.
- Persistence across reloads.
- System preference fallback works.
- Toggle does not cause full page reload.
- No console errors.
🗂 File organization rules
- Place theme utilities near existing global utilities.
- Place toggle component near other UI controls.
- Do not create deep new folder hierarchies.
- Follow existing naming conventions.
🧹 Clean code requirements
- Keep implementation small.
- No unused code.
- No commented-out large blocks.
- Document only where helpful.
- Follow existing lint rules.
🏁 Definition of done
Implementation is complete when:
- The app supports light and dark themes.
- User preference persists.
- No architectural paradigm was replaced.
- No large-scale refactor was required.
- No regressions are introduced.
- Solution feels native to the codebase.
🛑 Before implementing
You MUST:
- Analyze the codebase.
- Identify styling strategy.
- Identify root layout entry point.
- Identify component architecture.
- Explain your implementation plan.
- Then implement.
Do not implement blindly.
Resolving conflicts and ambiguity
If architectural conflicts arise: choose the simplest solution; favor minimalism and native browser capabilities; avoid adding dependencies unless absolutely required.
If anything is ambiguous: ask before proceeding.