Skip to content

feat(theme): ThemeProvider + useTheme foundation (closes #1030)#1122

Open
jeromehardaway wants to merge 1 commit into
masterfrom
feat/dark-mode-theme-context
Open

feat(theme): ThemeProvider + useTheme foundation (closes #1030)#1122
jeromehardaway wants to merge 1 commit into
masterfrom
feat/dark-mode-theme-context

Conversation

@jeromehardaway

Copy link
Copy Markdown
Contributor

Summary

First piece of the dark-mode track. Adds theme state management and the pre-paint script that prevents FOUC. Does not wire the provider into `_app.tsx` yet (that's #1031) or render any UI — strictly the foundation.

`src/contexts/theme-context.tsx`

  • `` holds the active preference (`"light" | "dark" | "system"`), reads it from `localStorage["vwc-theme"]` on mount, and writes the `dark` class to `` via Tailwind's class-based dark mode (`darkMode: "class"` is already set in `tailwind.config.js`).
  • In `"system"` mode the provider subscribes to `(prefers-color-scheme: dark)` so the UI flips live with the OS.
  • `useTheme()` throws when called outside the provider — missing setup fails loudly instead of silently degrading.
  • `THEME_PREPAINT_SCRIPT` exported as a string for `dangerouslySetInnerHTML`. Compact, dependency-free IIFE.

`src/pages/_document.tsx`

  • Inject the pre-paint script into `` so the resolved theme is on `` before React hydrates. Without it, every dark-mode page load flashes light.

Tests

`tests/contexts/theme-context.test.tsx` — 6 cases (6/6 pass):

  • Defaults to system, resolves to OS preference
  • Reads stored preference on mount
  • `setTheme` persists + toggles the html class
  • System mode flips live when OS changes
  • `setTheme` to explicit value stops live OS sync
  • `useTheme` throws outside the provider

Closes #1030. Unblocks #1031, #1032, #1033, #1034.

Test plan

Closes #1030.

src/contexts/theme-context.tsx exports ThemeProvider, useTheme, and a
pre-paint script. The provider holds the user's preference
(light/dark/system), reads it from localStorage on mount, subscribes to
(prefers-color-scheme: dark) when in system mode, and toggles the dark
class on <html> via Tailwind's class-based dark mode.

THEME_PREPAINT_SCRIPT is a dependency-free IIFE injected into
_document.tsx so the dark class is on <html> before first paint.
Without it, dark-mode users see a flash of light content on load.

Tests cover system resolution, stored preference, persistence, live OS
sync, system-to-explicit transition, and useTheme outside provider
throwing — 6 cases.

Does not wire the provider into _app.tsx yet (that's #1031).
@vercel

vercel Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vets-who-code-app Ready Ready Preview, Comment May 13, 2026 4:26pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the foundation for dark-mode theme state management and pre-paint theme application, supporting the upcoming provider integration and toggle UI work.

Changes:

  • Added ThemeProvider, useTheme, theme persistence, system preference syncing, and pre-paint script export.
  • Injected the pre-paint script into _document.tsx.
  • Added tests for theme defaults, persistence, OS syncing, and provider enforcement.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/contexts/theme-context.tsx Defines theme context, provider logic, hook, and inline pre-paint script.
src/pages/_document.tsx Injects the pre-paint script into the document head.
__tests__/contexts/theme-context.test.tsx Adds coverage for theme provider and hook behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +73
useEffect(() => {
const stored = readStoredTheme();
setThemeState(stored);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Dark Mode — Phase 0] Create ThemeProvider context + useTheme hook

2 participants