Skip to content

feat(web): add dark mode with system-default theme switcher - #468

Merged
nbyl merged 1 commit into
mainfrom
462-add-dark-mode
Aug 21, 2026
Merged

feat(web): add dark mode with system-default theme switcher#468
nbyl merged 1 commit into
mainfrom
462-add-dark-mode

Conversation

@nbyl

@nbyl nbyl commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

Adds dark mode per #462: a ThemeProvider managing light/dark/system modes (default system), a pre-paint script to avoid theme flash, a new DropdownMenu primitive, and a theme switcher in the Header next to the Logout button.

Related Issue

Closes #462

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactoring

Changes Made

  • components/theme-provider.tsx: context + useTheme hook; three modes with system default; resolves via prefers-color-scheme, follows live OS changes while in system mode; toggles the dark class + color-scheme on document.documentElement; persists to localStorage key metio-theme (cleared/invalid storage falls back to system); exposes resolvedTheme
  • web/index.html: inline pre-paint script resolving the theme before first render (no FOUC), kept in sync with the provider
  • ui/DropdownMenu.tsx: new shadcn-style primitive wrapping radix-ui's dropdown-menu (unified radix-ui package, same conventions as Select.tsx), exported via ui/index.ts
  • Header.tsx: Light/Dark/System radio dropdown rendered directly right of the Logout button (Sun/Moon icon swap); only shown with the user section
  • main.tsx / ThemedToaster.tsx: app wrapped in ThemeProvider; Sonner Toaster follows the resolved theme instead of hardcoded 'light'

Testing

  • make test passes
  • make verify-backend passes
  • cd web && npm run lint passes
  • cd web && npm run format:check passes
  • Manual testing completed:
    • new ThemeProvider tests: system default, live OS-preference changes, explicit choice overrides OS, persistence/reload, unknown-value fallback
    • Layout light-theme assertion replaced with provider-driven light/dark tests
    • Header tests: switcher placement next to Logout, hidden without user section, dropdown switching works end-to-end
    • test setup gained a matchMedia stub and in-memory localStorage (Node 26 ships it disabled without --localstorage-file)
    • coverage: 90.6% statements / 86.3% branches / 86.6% functions / 91.1% lines (≥80%)

Screenshots (if applicable)

N/A — visual change best reviewed with the dev server (cd web && npm run dev).

Add a ThemeProvider managing light/dark/system modes: it defaults to the
OS preference via prefers-color-scheme, follows live OS changes while in
system mode, toggles the `dark` class on <html> (matching the existing
@custom-variant setup), and persists the choice to localStorage. An
inline script in index.html resolves the theme before first paint to
prevent a flash of the wrong theme.

New ui/DropdownMenu.tsx primitive wraps radix-ui's dropdown menu. The
Header renders a Light/Dark/System dropdown directly right of the Logout
button, and the Sonner Toaster now follows the resolved theme instead of
a hardcoded light.

Layout's hardcoded light-theme assertion is replaced with provider-driven
tests; new tests cover switching, persistence, system fallback and live
OS changes (including a matchMedia stub and in-memory localStorage for
jsdom/Node 26).

Closes #462
@nbyl nbyl mentioned this pull request Aug 21, 2026
11 tasks
@nbyl
nbyl merged commit 278736d into main Aug 21, 2026
9 checks passed
@nbyl
nbyl deleted the 462-add-dark-mode branch August 21, 2026 14:07
nbyl added a commit that referenced this pull request Aug 21, 2026
…471)

## Summary

Documents the public GitHub Project [Metio
Development](https://github.com/users/nbyl/projects/7) in `AGENTS.md`
and clarifies that references to moving a ticket "in progress" or "in
review" mean setting the issue's `Status` field on this project.

## Related Issue

None

## Type of Change

- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [x] Documentation
- [ ] Refactoring

## Changes Made

- New **Project Board** bullet: project URL, `Status` field options
(`Backlog`, `Ready`, `In progress`, `In review`, `Done`), and how to
add/update issues on it via `gh project item-add`
- Fixed stale Linear reference in the **Pull Requests** bullet — now
points to the project's Status field instead

## Testing

- [x] `make test` passes
- [x] `make verify-backend` passes
- [x] `cd web && npm run lint` passes
- [ ] `cd web && npm run format:check` passes — **pre-existing**
Prettier warnings in 7 files merged with #468 (unrelated to this
docs-only PR)
- [x] Manual testing completed (docs-only change; no code paths
affected)
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.

add dark mode

1 participant