4.5 KiB
Context
The app is a Next.js 16 candlestick chart annotation tool using Tailwind CSS 3 with shadcn-ui style CSS variables (HSL values in :root). Tailwind is already configured with darkMode: ["class"], meaning the .dark class on <html> controls dark mode. Currently only light-mode CSS variables are defined in globals.css, and the hacker-theme spec describes a dark-only neon green aesthetic. The app needs to support both themes with system detection and manual override.
Goals / Non-Goals
Goals:
- Detect system color scheme preference (
prefers-color-scheme) and apply matching theme on first load - Provide a manual toggle in the UI to switch between dark, light, and system modes
- Persist the user's choice in
localStorage - Avoid flash of wrong theme (FOUC) on page load
- Define dark-mode CSS variables that maintain the hacker-theme aesthetic
- Define light-mode CSS variables that provide a clean, readable alternative while keeping monospace typography and terminal feel
Non-Goals:
- Custom user-defined color themes beyond dark/light
- Per-component theme overrides
- Server-side theme detection (SSR will use system default, hydration applies preference)
Decisions
1. Use next-themes for theme management
Choice: Install and use next-themes library.
Rationale: next-themes is the standard solution for Next.js theme switching. It handles:
- System preference detection via
prefers-color-schememedia query - Adding/removing the
.darkclass on<html>(matches our existingdarkMode: ["class"]Tailwind config) localStoragepersistence- FOUC prevention via inline script injection
- SSR compatibility
Alternatives considered:
- Custom React context + useEffect: Would work but requires reimplementing FOUC prevention, system detection, and localStorage sync.
next-themesis battle-tested and small (~2KB). - CSS-only with
prefers-color-scheme: No manual toggle capability, which is a requirement.
2. Three-mode toggle: System / Light / Dark
Choice: Offer three modes — System (follows OS), Light (forced), Dark (forced).
Rationale: Users who want to match their OS get automatic switching. Users who prefer a specific theme can lock it in. This is the standard UX pattern.
3. CSS variable approach for both themes
Choice: Define :root (light) and .dark (dark) CSS variable sets in globals.css.
Rationale: The existing setup already uses HSL CSS variables consumed by Tailwind. Adding a .dark selector block with dark-mode values requires zero changes to component code — all bg-background, text-foreground, etc. classes automatically switch. The hacker-theme neon green palette maps to the dark variant. The light variant uses neutral/slate tones with green accents.
4. Theme toggle placement
Choice: Place the theme toggle button in the sidebar, near the top or bottom of the tool list.
Rationale: The sidebar is always visible and is the existing control surface. A small icon button (sun/moon/monitor icons from lucide-react) keeps it unobtrusive. Three-state cycle: click rotates through system → light → dark.
5. ThemeProvider wraps the app in layout.tsx
Choice: Wrap {children} with <ThemeProvider> from next-themes in the root layout.
Rationale: This is the standard integration point. The provider must be a client component, so we'll create a thin ThemeProvider wrapper component.
Risks / Trade-offs
-
FOUC on first load:
next-themesinjects a blocking script to prevent this, but in rare edge cases (slow JS) users might see a brief flash. → Mitigation:next-themeshandles this well out of the box;attribute="class"config ensures Tailwind picks it up immediately. -
Chart library theming:
lightweight-chartshas its own color configuration that's set programmatically, not via CSS. → Mitigation: The chart component will need to read the current theme and pass appropriate colors to the chart instance. Listen to theme changes and update chart colors accordingly. -
Hacker theme divergence: The light theme won't have neon glow effects or scanlines — these only make sense on dark backgrounds. → Mitigation: This is intentional. The light theme keeps monospace fonts and terminal structure but uses conventional colors for readability.
-
Component styles relying on hardcoded colors: If any components use hardcoded hex colors instead of CSS variables, they won't switch. → Mitigation: Audit components during implementation and replace hardcoded values with CSS variable references.